Skip to content

fix(producer): deduplicate scene font faces before embedding - #6395

Merged
jrusso1020 merged 3 commits into
heygen-com:mainfrom
Dante-dan:fix/6381-deduplicate-render-fonts
Oct 26, 2026
Merged

jrusso1020 merged 3 commits into
heygen-com:mainfrom
Dante-dan:fix/6381-deduplicate-render-fonts

Conversation

@Dante-dan

@Dante-dan Dante-dan commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

What

Deduplicate equivalent assembled @font-face rules before the render compiler embeds local font data.

Why

Repeating local font rules in scenes repeats their full data URIs in export HTML. This addresses the font expansion reported in #6381; I could not validate the original private project or the Linux Desktop navigation timeout end to end.

Related work

Fixes #6381. This follows the reporter's consolidation approach and reuses the existing core dedupeFontFaceRules operation used by bundleToSingleHtml. #5098 addresses the separate ASCII-folding crash; #6134 bounds bundler asset expansion rather than deduplicating render font declarations. No overlapping implementation PR was found.

How

Apply the shared deduplicator to active, unconditional assembled styles before coalescing and embedding. Keep the last equivalent declaration in its original position. Different descriptors, nested conditional rules, alternate/media styles and inert template styles remain intact. Source scene files are unchanged.

Test plan

  • New regressions cover repeated fonts across assembled scenes, last-occurrence order around a different descriptor, and preservation of conditional/inactive styles.
  • Five local-font tests pass. The broader compiler file initially had 160 passing / 4 failing tests (three 5-second timeouts and an unexpected Inter/Montserrat fetch assertion); all eight tests in those affected groups pass when rerun with a 30-second timeout.
  • Changed-file lint/format, full format check, tracked-artifact check, and the pre-commit changed-file Fallow audit pass.
  • Actual size comparison on a synthetic 92-scene fixture with 53 local font paths/families, each backed by a copy of the repository's public 76,340-byte WOFF2 asset. At the same post-embedding boundary, HTML shrinks from 496,817,582 bytes / 4,876 font-face rules to 5,451,162 bytes / 53 rules (98.9%). The patched full compiler also returns 5,451,162 bytes. The baseline full compile was stopped during subsequent large-document processing; this is a size result, not a render-time benchmark.
  • First-frame/end-to-end export: the existing official producer font-variant-numeric regression fixture compiles, then Chromium fails to launch with macOS MachPortRendezvous / bootstrap_check_in ... Permission denied (1100). No frames were captured,.
  • The synthetic pressure fixture's CLI lint passes (93 files); CLI check reports intentional stacked-scene occlusion and an overlap-pass deadline. It is not a visual acceptance fixture and is not committed.
  • Full workspace build/lint/typecheck/test suite is not claimed passing. Initial commands hit the local Node version-manager cache write restriction; broad retries were stopped under resource contention. See any subsequent CI results for full-workspace coverage.
  • Troubleshooting documentation updated.

Tested source is commit 439b5ff38ad305caa0157053e84add8b83a9e6ec. The long-running local pre-commit typecheck was interrupted and remains unverified; the already-passed Fallow audit and that unfinished hook were excluded when recording the signed commit.

Review follow-up

Raised only @hyperframes/producer.unpacked from 2,840,135 to 3,012,450 bytes as requested in review. Build measured 2,869,000 bytes for the producer package with the render deduplication code; 3,012,450 is that measured size × 1.05, following the verifier's headroom formula. The packed budget is unchanged.

Preview and render now share isAlwaysAppliedStyle from core rather than duplicating its filter or exporting the CSS key. The troubleshooting section explains that declarations must match (family, src, and descriptors), and recommends matching repeated rules or a shared root style when export HTML remains large.

Revision verification: the 5 local font-embedding tests pass; the three core suites returned 295 passed and 3 default-5-second timeouts, and the affected groups passed on a targeted rerun (4 passed) with a 30-second timeout. Producer typecheck, core/producer builds, lint, formatting, comment citations, and comment ratchet passed. Final pre-commit and packed-manifest results are recorded below. No end-to-end claim is made for the original private project or the Linux Desktop timeout.

Normal pre-commit checks passed, including core/studio/scripts typechecks and Fallow. The packed-manifest verifier could not run to completion locally because pnpm was unavailable (the local fallback executable returned ENOEXEC); the package-size/Studio gates still need remote CI verification.

@mintlify

mintlify Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Preview approval required

This pull request comes from a fork, so Mintlify won't build a preview until someone with write access approves commit b50f05e.

Approve preview

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for picking this up, and for the careful write-up and size numbers on the issue. The approach looks right to me.

  • Reusing dedupeFontFaceRules with the same "always applied" filter the bundler uses (htmlCompiler.ts:945-952) keeps export consistent with preview.
  • Keeping the last copy matches how the browser resolves identical faces, and media, alternate and template styles plus nested rules are left alone.
  • Running it before embedLocalFontFaces means only paths get parsed, never font bytes.

I ran the local font embedding tests: 5/5 pass three times in a row. Each new test fails when I break the code: with the dedupe removed, with the filter widened to every style, and with a keep-first dedupe. Nice coverage.

Blocker

  • Build is red at verify:packed-manifests: "@hyperframes/producer unpacked size 2869000 bytes is over its budget of 2840135 by more than 1%". The budget allows 1% over the last recorded size, and the added producer code pushes the bundle just past that limit. Build is green on the base commit and on main. Please raise @hyperframes/producer.unpacked in scripts/package-size-budgets.json and add a line to the PR body saying why. The verifier's own formula (size x 1.05) gives 3012450.
  • Studio: edit accuracy gate fails only because its shards depend on Build and were skipped, so it should go green with the same fix.

Nits

  • htmlCompiler.ts:945-948: this repeats the bundler's private isAlwaysAppliedStyle (packages/core/src/compiler/htmlBundler.ts:728-730). Exporting that function from core, rather than UNCONDITIONAL_CSS_KEY, would keep one definition for preview and render.
  • docs/guides/troubleshooting.mdx:218: one sentence telling readers what to do would help, for example that only identical declarations (same family, src and descriptors) are merged, so a project that is still large should make its repeated scene font rules match exactly or declare them once in a shared root style.

Verdict: REQUEST_CHANGES
Reasoning: The code and tests look good, but this PR pushes the producer package over its size budget, which fails Build and with it the edit-accuracy gate. Raising the budget with a stated reason should be all that's needed.

@Dante-dan

Copy link
Copy Markdown
Contributor Author

Addressed in f37a6f7: the producer unpacked budget is now 3,012,450 bytes (the reported 2,869,000 × 1.05), with the reason added to the PR body. Preview and render share isAlwaysAppliedStyle, and the docs now explain exact declaration matching and a shared root style.

The 5 font-embedding tests and producer typecheck pass. The three core suites had 295 passes and 3 default-timeout failures; a targeted rerun of the affected groups passed all 4 tests with a 30-second timeout. Core/producer builds and the scoped lint, format, and comment checks pass. Full workspace/Studio edit-accuracy CI is left to the new head; the original project's end-to-end limitation still applies.

Normal pre-commit checks passed, including core/studio/scripts typechecks and Fallow. The packed-manifest verifier could not run to completion locally because pnpm was unavailable (the local fallback executable returned ENOEXEC); the package-size/Studio gates still need remote CI verification.

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick and thorough follow-up. All three earlier points are addressed:

  • The producer unpacked budget is raised, and the PR body gives the reason.
  • isAlwaysAppliedStyle now lives in core (scriptRuns.ts:125) and is shared by the bundler and the render compiler. UNCONDITIONAL_CSS_KEY is private again.
  • The troubleshooting note now tells readers what to do.

I re-ran the local font embedding tests at this head: 5/5 pass three times in a row, and the producer typecheck is clean. Both new tests still fail when I break the code (filter widened to every style, dedupe removed).

One thing left before this can merge. The branch now conflicts with main, so none of the CI workflows ran on this commit. That includes Build, which is the check the budget change needs to clear. Two files conflict:

  • docs/guides/troubleshooting.mdx was removed on main when Troubleshooting became a single page (#6380). The same section now lives in docs/help.mdx, under "The render looks different from preview" (around line 344). Please move the new "Repeated scene fonts make export HTML very large" subsection there.
  • scripts/package-size-budgets.json: main has since raised the producer unpacked budget to 2897429. Keeping your 3012450 should be fine. If Build still complains after the rebase, use the size it reports.

Once it is rebased and CI is green, I'm happy to approve.

Verdict: REQUEST_CHANGES
Reasoning: Every earlier point is fixed and the tests hold up, but the branch conflicts with main (moved docs page, budget file), so CI never ran on this head and the size budget fix is unverified.

@jrusso1020

Copy link
Copy Markdown
Collaborator

@Dante-dan thanks for this, it's the right fix. We confirmed it on current main: 92 scenes with 5 fonts go from 40.9 MB to 0.48 MB. To land it:

  1. Rebase onto main. docs/guides/troubleshooting.mdx was removed in docs: one Troubleshooting page, four tabs, and the start pages folded #6380, so please move the new section into docs/help.mdx under "The render looks different from preview".
  2. Drop the scripts/package-size-budgets.json change. The PR adds about 1 KB and main's budget already covers it.
  3. Change "Refs Render export times out before the first frame when repeated font declarations inflate HTML to 524 MB #6381" to "Fixes Render export times out before the first frame when repeated font declarations inflate HTML to 524 MB #6381" in the description.

This one is blocking real exports, so if we don't see an update in the next hour or so we'll push the rebase onto your branch ourselves and keep your commits.

Dante-dan and others added 3 commits October 26, 2026 15:23
The troubleshooting guide this section originally targeted was folded
into docs/help.mdx on main, so the section now lives under the render
troubleshooting entries there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@jrusso1020
jrusso1020 force-pushed the fix/6381-deduplicate-render-fonts branch from f37a6f7 to b50f05e Compare October 26, 2026 15:33
@jrusso1020

Copy link
Copy Markdown
Collaborator

We've rebased this onto main for you, keeping your commits. The docs section moved to docs/help.mdx since the troubleshooting page was removed, and we dropped the size budget change since the package fits main's budget. Thanks for the fix!

@miguel-heygen miguel-heygen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed exact head b50f05e5e9527911afaefd3c62574fe27932e0f8 against base 2be8bc2b9e8d6f9306a55b1c6af64c0049ce7c70.

The earlier blockers are resolved: the docs section is in docs/help.mdx:354, there is no package-budget delta, and current-head Build and Studio edit-accuracy runs succeeded. The merge tree with the captured base is clean. isAlwaysAppliedStyle is now shared by preview and render (scriptRuns.ts:125); the producer applies existing last-copy font deduplication before local-font embedding (htmlCompiler.ts:944-952,2184). Conditional/inactive styles retain their rules.

Executed: five producer local-font tests and 93 core compiler/style tests passed in an exact-source archive using existing dependencies. Removing deduplication makes the new order/size regression fail; widening the filter makes the conditional-style regression fail. Both mutations were restored and the five tests pass again. An independent synthetic 92-scene/five-local-font fixture produced 14,582,848 bytes / 460 faces with deduplication disabled versus 188,377 bytes / five faces at this head. This measures compiled HTML, not render speed or the original private project.

CI: snapshot has 80 successful checks, 45 skipped, one cancelled optional Studio/player capture and one neutral fork-preview approval. Successful Build 121669281358 and edit-accuracy 121680711684 are on this exact head; later duplicates are skipped. The single Build-log download attempt returned HTTP 503, so I am relying on GitHub's completed check result, not claiming a local package-size build or inspected successful log.

Scope/limits: inspected all six changed files, shared dedupe/filter implementation and render call ordering, prior review bodies/comments and consumer imports. Initial local collection needed the normal generated runtime artifacts and an existing jsdom dependency link; no installation was performed. No original-project export, first-frame/browser render, Linux Desktop timeout reproduction or hosted fork-docs preview was executed. This compiler-focused approval does not claim those acceptance results.

Verdict: APPROVE
Reasoning: The prior conflict/CI blockers are cleared at the captured head. Targeted tests, negative controls and the synthetic compile witness support the font-deduplication behavior without changing authored scenes or conditional styles.

— Magi

@jrusso1020
jrusso1020 added this pull request to the merge queue Oct 26, 2026
Merged via the queue into heygen-com:main with commit 9ecb13e Oct 26, 2026
126 of 127 checks passed
SanHsien added a commit to SanHsien/hyperframes that referenced this pull request Oct 26, 2026
Merge 20 more upstream commits (acd0bc3..ad4d3fa, v0.8.205: heygen-com#6403,
heygen-com#6363, heygen-com#6209, heygen-com#6395, heygen-com#6424, heygen-com#6426, heygen-com#6442, heygen-com#6430, heygen-com#6402, heygen-com#6435, heygen-com#6439,
heygen-com#6449, heygen-com#5436, heygen-com#6455 and docs). No conflicts. Record the 11 open PRs
heygen-com#6440-heygen-com#6458 and issue heygen-com#6443 in docs/DECISIONS.md (none adopted), re-check
the earlier open PRs, and advance the commit watermark to ad4d3fa, the
PR watermark to heygen-com#6458 and the issue watermark to heygen-com#6443.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Render export times out before the first frame when repeated font declarations inflate HTML to 524 MB

3 participants