Skip to content

fix: hold external sub-compositions in render mode + regenerate baseline#918

Merged
miguel-heygen merged 6 commits into
mainfrom
refactor/unify-subcomp-inlining
May 17, 2026
Merged

fix: hold external sub-compositions in render mode + regenerate baseline#918
miguel-heygen merged 6 commits into
mainfrom
refactor/unify-subcomp-inlining

Conversation

@miguel-heygen
Copy link
Copy Markdown
Collaborator

@miguel-heygen miguel-heygen commented May 17, 2026

Summary

Changes

File Change
packages/core/src/compiler/inlineSubCompositions.ts NEW — shared sub-composition inlining function (319 lines)
packages/core/src/compiler/htmlBundler.ts Replace inline sub-comp loop with call to shared function (-130 lines)
packages/core/src/compiler/index.ts Export shared function + types
packages/core/src/runtime/init.ts Also check data-composition-file in visibility computation
packages/core/src/runtime/init.test.ts Add test for compiled external composition hosts
packages/producer/src/services/htmlCompiler.ts Replace 194-line inlineSubCompositions with 90-line wrapper using shared function
packages/producer/tests/style-12-prod/output/ Regenerated baseline (Docker)

Root cause of #911

The producer and core bundler had parallel implementations of sub-composition inlining (~200 lines each). The core bundler correctly set data-composition-file after inlining; the producer didn't. PR #917 added a runtime check for data-composition-src, but that attribute is stripped by both inliners — only data-composition-file survives to runtime.

The fix: one shared function, one source of truth for data-composition-file.

Architecture

The shared function accepts environment-specific callbacks:

  • resolveHtml: Core bundler reads from filesystem; producer checks a pre-compiled map first
  • parseHtml: Both pass their DOM parser (core uses parseHTMLContent, producer uses linkedom's parseHTML)
  • flattenInnerRoot: Core bundler's prepareFlattenedInnerRoot (strips attrs, marks inner root); producer skips this
  • readVariableDefaults / parseHostVariables: Core bundler handles variable merging; producer skips

Producer-specific logic (explicit pixel dimensions on hosts) stays in the producer wrapper.

Test plan

🤖 Generated with Claude Code

miguel-heygen and others added 2 commits May 17, 2026 16:14
The sub-composition visibility fix (2b46565) correctly holds external
compositions through their authored data-duration. This changes
style-12-prod output from t=8.26s onward: the mondrian-colors
sub-composition now stays visible instead of going black when its GSAP
timeline ends.

Baseline regenerated inside Docker per CLAUDE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both the core bundler (htmlBundler.ts) and the producer (htmlCompiler.ts)
had parallel ~200-line implementations of sub-composition inlining. This
divergence caused bug #911 (producer didn't set data-composition-file).

Extract the shared logic into core/compiler/inlineSubCompositions.ts:
- Single function handles: template/body extraction, CSS/script scoping,
  asset path rewriting, data-composition-file attribution, content injection
- Callers provide environment-specific callbacks (HTML resolution, parsing,
  variable handling, inner root flattening)
- Core bundler passes its advanced features (runtime IDs, variables,
  inline style rewriting, inner root flattening)
- Producer passes a simpler resolver (map + filesystem fallback) and
  adds pixel sizing post-hoc

Net: -215 lines, one source of truth for sub-comp inlining.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread packages/core/src/compiler/inlineSubCompositions.ts Dismissed
miguel-heygen and others added 4 commits May 17, 2026 16:51
When linkedom parses a fragment like `<div data-composition-id="X">...
</div>`, the div becomes the documentElement and body is empty.
contentDoc.body?.innerHTML returns "" losing the composition wrapper.

Fall back to contentDoc.documentElement?.outerHTML when body content
is empty, preserving composition IDs for sub-compositions where the
host data-composition-id differs from the inner root's.

Fixes style-1-prod regression (captions sub-comp has host id
"captions-comp" but inner root id "captions").

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The sub-composition inlining now correctly preserves composition IDs
when the host data-composition-id differs from the inner root's
(e.g., host "captions-comp" with inner root "captions"). The captions
layer renders with proper scoping, changing visual output.

Baseline regenerated inside Docker per CLAUDE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sub-composition visibility fix changes output for compositions
with external sub-compositions. Baseline regenerated in Docker.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Regenerated baselines for all regression tests with sub-compositions
in the cancelled shards: style-3-prod, style-5-prod, style-9-prod,
style-15-prod, style-16-prod, style-17-prod, style-18-prod,
sub-composition-video, many-cuts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@miguel-heygen miguel-heygen merged commit 7a1e876 into main May 17, 2026
37 of 44 checks passed
@miguel-heygen miguel-heygen deleted the refactor/unify-subcomp-inlining branch May 17, 2026 18:22
jrusso1020 added a commit that referenced this pull request May 17, 2026
`Dockerfile.test:56` installed `chrome-headless-shell@stable` via
`@puppeteer/browsers`. `@stable` is a moving tag, so every Chrome stable
bump shifted pixel output enough to fail PSNR on the golden baselines.
The regression suite silently broke whenever Docker.test rebuilt
against a freshly-promoted stable.

Pin to `chrome-headless-shell@148.0.7778.167` — the Chrome 148 stable
build that `@stable` currently resolves to, matching what most goldens
on `main` were captured against. Comment notes that future bumps must
be paired with `docker:test:update` so the pin and the baselines stay
in lockstep.

Also regenerates the `style-12-prod` golden baseline. PR #918 regenerated
it once at b9bdc80, but that commit landed *before* the
`refactor: extract shared inlineSubCompositions from bundler and producer`
(581e7a7) and the linkedom-fragment fix (754b0ed) in the same stack.
The compiler refactor changes `__hfRootSelector` from `null` to a scoped
`[data-composition-id="..."]` selector in the inlined sub-compositions,
which affects the rendered output. style-12-prod was the one fixture in
that stack that didn't get a second regen pass after the refactor, so
it has been failing on plain `origin/main` (PSNR ~13 from frame 8.26s
onward — the mondrian-colors blocks no longer match expected).
The new baseline regenerated under this pin passes at PSNR 62-102 dB.
jrusso1020 added a commit that referenced this pull request May 17, 2026
Two related fixes pulled out of CI failures on the Chrome pin run:

1. **regression-harness PSNR-parse crash on many-cuts.** Container
   duration includes audio padding past the last video frame (many-cuts:
   5.654s container, 5.6s of video at 30fps = 168 frames). At i=99 the
   raw container duration mapped to time 5.59746s → frame index 168
   (round(5.59746 * 30)), which is one past the last frame the stream
   contains. ffmpeg's `psnr` filter emits no `average:` line for a
   non-existent frame, so the harness crashed with `Unable to parse
   PSNR output at 5.59746s`. The fix subtracts one frame interval from
   the sampling duration so the last checkpoint always lands on a
   frame the video stream actually contains. PR #918 admin-merged
   through this same failure on shard-2 (so main is currently red on
   many-cuts), and Miguel's regen via `--update` didn't catch it
   because `--update` only writes the snapshot — it doesn't validate.

2. **style-1-prod baseline regen.** Same pattern as style-12-prod:
   PR #918's regen was done before / between the `refactor: extract
   shared inlineSubCompositions from bundler and producer` (581e7a7)
   and the linkedom-fragment fix (754b0ed), so the committed baseline
   doesn't match what the compiler now emits. Reproduced locally:
   frames 14.62s onward fail at PSNR ~10-16 because the graphics
   sub-composition layer (`#a-roll-frame` overlay) now correctly
   renders through host duration but was absent in the committed
   baseline. Regenerated under the Chrome 148.0.7778.167 pin from
   this PR — now passes at PSNR 53-62 dB across all checkpoints.
pull Bot pushed a commit to beauNate/hyperframes that referenced this pull request May 18, 2026
Sub-comp visibility fix (PR heygen-com#918) changed rendered output for these two
tests but the baselines on main were stale. Regenerated inside
Dockerfile.test to match CI's Chrome + ffmpeg build.

Co-Authored-By: Claude Sonnet 4.6 <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.

Sub-composition slot goes black after GSAP timeline ends, regardless of host data-duration

2 participants