fix(cli): settle post-seek font subset loads before audit screenshots#2251
Conversation
miga-heygen
left a comment
There was a problem hiding this comment.
#2251 — settle post-seek font subset loads before audit screenshots
Verdict: LGTM 🟢
Fixes CJK snapshot race: unicode-range @font-face subsets only start loading when first laid out. After a seek reveals new glyphs, fonts.ready is already resolved (no subsets were requested yet), so the old code screenshotted blank CJK glyphs.
Fix: force synchronous layout (document.body.offsetHeight), wait one rAF for the browser to flip fonts.status to "loading", THEN await fonts.ready. If nothing is loading after the rAF, resolves immediately (no wasted wait on Latin-only comps). Bounded by 500ms deadline.
On by default for all visual-audit seek callers (snapshot, check, compare, validate, layout). waitForFontsMs: 0 disables. Existing render-grade callers with animationFrameSettle: "none" explicitly pass 0 — no behavior change for those paths. Clean.
Review by Miga
miguel-heygen
left a comment
There was a problem hiding this comment.
Reviewed current head 8821646. Forced layout + rAF + fonts.ready ordering is bounded by the timeout and hidden-document rAF is covered by the deadline; waitForFontsMs=0 preserves opt-out. Targeted tests and required checks are green. APPROVE.
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Reviewed at 8821646.
Clean fix for the CJK-subset race — classify: this is a NARROW, not a preempt. Forcing a synchronous layout via void document.body?.offsetHeight; at packages/cli/src/capture/captureCompositionFrame.ts:308-314 triggers font resolution, which registers pending subsets in document.fonts synchronously; the one-rAF wait gives that registration a tick to reflect in fonts.status; the status !== "loading" || !ready short-circuit avoids awaiting a stale already-resolved ready promise from a previous generation. Bounded by the setTimeout(resolveWait, ms) deadline so fonts.ready never resolving can't hang the audit.
Opt-out semantics: undefined → default 500ms, explicit 0 → disabled entirely. Tests cover the default-on flip and the waitForFontsMs: 0 opt-out. Default-on is the right flip for the visual-audit surface (snapshot / check / compare / validate / layout are all users of seekCompositionTimeline and none should screenshot before glyphs paint) — the existing internal call at the layered-audit path already passes waitForFontsMs: 0 so the new default doesn't double-charge that route.
LGTM from my side.
jrusso1020
left a comment
There was a problem hiding this comment.
Approving per the channel stamp-flow: Rames-D and Magi both cleared, CI clean at 8821646.
8821646 to
8bbd5a6
Compare
a2a6810 to
1b95af8
Compare
The base branch was changed.

What
Turns on a post-seek font-readiness wait by default for every visual-audit seek path (snapshot, check, compare, validate, layout) via
waitForCompositionFonts. A seek can reveal glyphs whose font subsets were never requested before — forcing layout, giving the loader one frame, then awaitingdocument.fonts.ready. PasswaitForFontsMs: 0to opt out.Why
Multiple wild reports: Traditional Chinese and Microsoft YaHei text (declared via local
@font-face) came back blank incheck/snapshot output despite the font being present and correctly rendering in the real browser/draft render. CJK fonts commonly split into unicode-range subsets that only start loading once something actually lays out with that codepoint range — awaitingfonts.readyimmediately (the old behavior) raced the load request.Validation
Updated + new unit tests in
captureCompositionFrame.test.ts(24 tests, including the new default-on behavior and thewaitForFontsMs: 0opt-out). Full CLI capture test suite green.🤖 Generated with Claude Code