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.
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