Flush the GSAP proxy queue synchronously during capture session initialization and parallelize independent media/font/tailwind readiness waits.
Closes#1715.
Co-authored-by: Miguel Angel Simon Sierra <miguel.sierra_miga@heygen.com>
* fix(producer): retry probe stage on transient browser errors (#1687)
The distributed render plan stage crashes when headless Chrome encounters
a transient frame detachment ("Navigating frame was detached") during
browser probe, with no retry logic. The plan tarball is never uploaded,
and all downstream chunk workers fail with S3 404.
Add a retry-with-fresh-session mechanism to the probe stage:
- `isTransientBrowserError()` classifier in the engine identifies 9
known transient Puppeteer/Chrome errors (frame detached, target closed,
session closed, protocol error, page crashed, execution context
destroyed, etc.).
- `runProbeStage()` wraps browser session creation + initialization in a
retry loop (max 2 attempts). On transient error: logs structured
diagnostics (attempt, isTransient, error message, elapsed time), closes
the crashed session cleanly, creates a fresh browser, and retries. Non-
transient errors throw immediately without consuming retry budget.
- 17 unit tests for the error classifier, 3 integration tests for retry
behavior (successful retry, immediate throw on non-transient, exhaust
retry budget on persistent transient).
Closes#1687
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review feedback — widen retry scope, deduplicate patterns
- Move createCaptureSession inside the retry try/catch so browser launch
failures (Failed to launch the browser process, ECONNREFUSED) are also
retried — not just initializeSession errors.
- Deduplicate transient error patterns: remove "Protocol error.*Target
closed" (subsumed by "Target closed") and "Navigation failed because
browser has disconnected" (subsumed by "browser has disconnected").
- Add browser launch failure patterns: "Failed to launch the browser
process" and "ECONNREFUSED".
- Add test for createCaptureSession transient throw (browser launch retry).
- Update test mock comment to document sync requirement with engine
pattern list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(compiler): skip CSS var() in font resolver — fixes FONT_FETCH_FAILED on distributed renders
The font scanner treated `var(--ui-font)` as a literal font family name,
causing fail-closed distributed renders to throw FONT_FETCH_FAILED for
any composition using CSS custom properties in font-family declarations.
CSS var() expressions resolve at browser paint time, not at compile time.
The regex-based font scanner cannot resolve them statically — skip them
and let headless Chrome handle variable substitution during render.
Closes#1654
— Miga
* test(regression): add distributed css-var-fonts fixture
Regression test for compositions that use CSS custom properties in
font-family declarations. Exercises the var() skip guard in
extractRequestedFontFamilies() under the distributed renderer's
fail-closed font resolution path.
Baseline needs to be generated on first CI run with --update.
— Miga
* fix(compiler): address review feedback — mixed declaration test + validator TODO
Add unit test verifying concrete fonts alongside var() in mixed
declarations still get resolved (non-aggression pin).
Add TODO(#1654) in validateNoSystemFonts for the var()-as-primary gap
flagged by both reviewers.
— Miga
* fix(test): correct stale 4xx fail-closed test expectations
The 4xx tests expected no throw, but that was the contract before #1255
added the system font capture path (Path 3). Post-#1255, a font that
gets 4xx from Google Fonts AND isn't a bundled alias AND has no system
font IS genuinely unresolvable — fail-closed mode should throw.
The 4xx distinction still matters at the fetch level (no retry, treated
as deterministic "not served"), but at the final unresolved check, a
completely unresolvable font must throw regardless of the HTTP status
that caused the Google Fonts path to return empty.
Updated tests to match the actual contract: 4xx + unresolvable = throw.
Also set allowSystemFontCapture: false to match how distributed renders
(plan.ts:799) actually call the function.
— Miga
---------
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>