Commit Graph
1132 Commits
Author SHA1 Message Date
Miguel Ángel e2f7f6a58a fix: add regression fixtures with golden baselines + address review
- Create sub-comp-t0 and sub-comp-id-selector as proper regression tests
  under packages/producer/tests/ with golden MP4 baselines
- Add both to shard-7 in regression.yml
- Add clarifying comment on activateNestedChildTimelines scope
- Confirm test fixture network safety in comment
2026-05-19 15:59:09 -04:00
Miguel Ángel 0d12a465a3 fix: activate nested child timelines during renderSeek
The renderSeek override in init.ts called seekTimelineAndAdapters() which
only did rootTimeline.totalTime(t) without activating child timelines.
GSAP does not propagate totalTime() to internally paused children.

Also simplifies pollSubCompositionTimelines to always call rebind when
timelines are ready, removing the before/after count comparison that
could skip the rebind on fast page loads.
2026-05-19 15:42:07 -04:00
Miguel Ángel 29ad7df90c style: format fixture HTML files 2026-05-19 15:41:38 -04:00
Miguel Ángel 60cdf8c66b test: add regression fixture for sub-comp #ID selector scoping divergence
The producer inlining path strips the inner root element (taking innerHTML
when compId matches), losing the id attribute. The bundler path preserves it
via flattenInnerRoot + data-hf-authored-root-id. This causes #ID selectors
in sub-comp CSS and GSAP to fail silently during render while working in
preview.

Adds a minimal fixture with a sub-comp using #intro scope to catch this
divergence in future compiler changes.
2026-05-19 15:38:59 -04:00
Miguel Ángel 7354d61371 chore: release v0.6.24 v0.6.24 2026-05-18 22:00:56 -04:00
Miguel Ángel 72a18a0116 Merge pull request #947 from heygen-com/feat/studio-blocks-panel
feat(studio): full Blocks panel — browse, search, add, drag-and-drop registry items
2026-05-19 03:58:25 +02:00
James Russo 2d566338ae Merge pull request #944 from heygen-com/cleanup/fallow-auto-fix
refactor: drop unused exports detected by fallow auto-fix
2026-05-18 21:19:44 -04:00
Miguel Ángel ffbc18ad31 feat(studio): full Blocks panel — browse, search, add, drag-and-drop registry items
Adds a Blocks tab to the Studio left sidebar with the full 78-item registry
catalog (58 blocks + 20 components). Users can browse by category, search by
title/description, preview CDN-hosted poster thumbnails with video-on-hover,
and install items on-demand with one click or drag-to-timeline.

Core changes:
- BlockCategory type + resolveBlockCategory() for 7 categories (Captions, VFX,
  Transitions, Effects, Social, Data, Scenes)
- Registry API routes: GET /api/registry/blocks (catalog) + POST install
- StudioApiAdapter extended with listRegistryCatalog + installRegistryBlock
- Vite adapter reads from disk; CLI adapter fetches from GitHub (24h cache)
- BlockParam interface + params on 6 blocks for future parameter controls

Studio UI:
- 4th sidebar tab "Blocks" with responsive grid, category pills, search bar
- BlockCard: CDN poster thumbnail, video autoplay on hover, duration + WebGL badges
- On-demand install: blocks append as sub-compositions on timeline; components
  overlay at start=0 spanning full duration with transparent background patching
- TIMELINE_BLOCK_MIME drag-and-drop to timeline
- BlockParamsPanel (Phase 3 scaffold) auto-opens for parameterized blocks

Registry manifests:
- All 58 blocks backfilled with preview: { video, poster } CDN URLs
- All 20 components normalized to object format + poster URLs added
- 6 blocks annotated with params (Liquid Glass/Background, Portal, Chart,
  Logo Outro, Magnetic)
- flowchart-vertical preview generated and uploaded to CDN
2026-05-18 21:15:15 -04:00
James 7e0a447325 refactor: drop unused exports detected by fallow auto-fix
Run `fallow fix --auto-fixable` to remove `export` keywords from symbols
fallow's reachability analysis identifies as unused. Keeps only the cases
where the symbol is still referenced internally in its own file (so
removing `export` doesn't surface a new oxlint `no-unused-vars` error).

Result: fallow dead-code findings drop from 276 → 208 (68 fewer unused
exports), with no behavior change — each symbol is still defined and used
exactly the same way within its file.

Reverted ~20 files where fallow's auto-fix would have created cascading
"declared but never used" lint errors — those are cases where the symbol
isn't used at all, and properly cleaning them up means deleting the
declaration, not just dropping `export`. Better to land that as a
separate, narrower PR rather than mixing it into a mechanical de-export.

Also reverted four false positives where fallow missed real consumers:
- `captureCost.ts` (renderOrchestrator has two separate import blocks
  from the same module; fallow only saw the first)
- `propertyPanelHelpers.ts`, `domEditingLayers.ts` (real internal uses
  fallow's reachability missed)
- `render.ts` (functions imported via `await import()` dynamic import,
  which fallow's static analysis doesn't follow)

Test plan: bun run --filter '*' typecheck (clean), oxlint + oxfmt clean,
cli/core/studio/engine vitest suites pass (335 + 917 + 576 + 605 tests).
2026-05-19 00:51:56 +00:00
James Russo 3eec777a29 Merge pull request #942 from heygen-com/05-18-ci_add_fallow_audit_job_pr-scoped_new-only_gate_
ci: add fallow audit job (PR-scoped, new-only gate)
2026-05-18 20:46:55 -04:00
Miguel Ángel d873364746 test(producer): regenerate 7 stale regression baselines (#946)
## Summary

- Regenerate golden baselines for 7 regression tests that were deterministically failing due to stale baselines from before the recent renderer fixes
- Root cause: renderer changes (revert flattenInnerRoot, late-bind polling removal, conditional rebind, sub-comp timeline readiness polling) changed visual output, but baselines were never regenerated
- CI's `Detect changes` job was skipping shards on non-engine commits, masking the failures — making it look like flaky tests when in reality they failed every time shards ran

## Tests regenerated (all in Docker with pinned Chrome 148.0.7778.167)

| Test | Previously failed frames | Baseline age |
|---|---|---|
| `gsap-letters-render-compat` | 86/100 | old |
| `typegpu-adapter` | 76/100 | May 13 |
| `style-7-prod` | 60/100 | PR #368 (very old) |
| `style-15-prod` | 59/100 | May 17 (pre-renderer-fixes) |
| `style-18-prod` | 19/100 | old |
| `style-3-prod` | 7/100 | May 17 (pre-renderer-fixes) |
| `style-8-prod` | 1/100 | borderline |

## Test plan

- [x] All 7 tests pass locally in Docker with 0 failed frames
- [x] Verified deterministic — ran style-7-prod twice, same result both times
- [x] All 8 CI regression shards green
- [x] Other tests (style-1, 2, 4, 5, 6, 9, 10, 11, 12, 13, 16, 17, overlay-montage, sub-composition-video, vignelli-stacking, etc.) still pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-19 02:38:07 +02:00
Miguel ÁngelandClaude Sonnet 4.6 13846554f5 test(producer): regenerate gsap-letters-render-compat baseline
Missed in the initial batch — 86/100 frames were failing. Regenerated
in Docker with the same pinned Chrome build. Now passes with 0 failures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 23:43:13 +00:00
Miguel Ángel d9183ba27d Merge pull request #940 from heygen-com/worktree-fix+seek-slider-duration
fix(studio): seek slider respects effective timeline duration with appended blocks
2026-05-19 00:39:13 +02:00
Miguel Ángel d0669fa86c Merge pull request #943 from CypherPoet/docs/canonical-pipeline-page
docs: add canonical pipeline guide (#937)
2026-05-19 00:38:25 +02:00
Miguel Ángel 1a63a945e0 fix(studio): address review — GSAP-only fallback, drop dead alias, add tests
- [blocker] When playerAdapter is null (GSAP-only runtimes with no
  win.__player), the fallback path now uses the best available timeline
  adapter instead of returning null. Track timelineAdapter across the
  __timeline and __timelines paths, then use it as the base for
  createStaticSeekPlaybackAdapter.

- [nit] Remove dead baseAdapter alias — use bestAdapter directly.

- [tests] Add 4 tests: readTimelineDurationFromDocument with
  data-hf-authored-duration fallback, createStaticSeekPlaybackAdapter
  with seek-only adapter (no renderSeek), and pause lifecycle.
2026-05-18 18:28:34 -04:00
Miguel ÁngelandClaude Sonnet 4.6 5a6c3dd45f test(producer): regenerate 6 stale regression baselines in Docker
Renderer fixes (revert flattenInnerRoot, late-bind polling removal,
conditional rebind, sub-comp timeline readiness polling) changed visual
output but baselines were never regenerated. CI's Detect changes job
was skipping shards on non-engine commits, masking the failures.

Regenerated in Docker (Dockerfile.test) with pinned Chrome 148.0.7778.167:
- style-7-prod (60/100 frames failed, baseline from PR #368)
- style-15-prod (59/100 frames failed)
- style-18-prod (19/100 frames failed)
- style-3-prod (7/100 frames failed)
- style-8-prod (1/100 frames failed, borderline)
- typegpu-adapter (76/100 frames failed)

All 6 now pass locally with 0 failed frames.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:28:10 +00:00
Miguel Ángel 10af9655f2 fix(studio): restore root-id resolution comment in __timelines path 2026-05-18 18:20:34 -04:00
Miguel Ángel b32a62d4d1 fix(studio): seek slider respects full document timeline duration
getAdapter() returned the runtime player or GSAP timeline adapter directly
when its duration was > 0, even when the document's timeline (computed from
sub-composition data-start + data-hf-authored-duration attributes) extended
beyond that duration. This capped the seek slider, seek clamping, and
sub-composition visibility at the adapter's shorter value.

Now each adapter path checks whether the document duration exceeds the
adapter's own duration. When it does, the adapter falls through to
createStaticSeekPlaybackAdapter which wraps the runtime player with the
correct effective duration, allowing seeking and preview across the full
timeline range.
2026-05-18 18:15:33 -04:00
Miguel Ángel ca6069763d fix(studio): patch runtime clock when document duration exceeds player duration
When the runtime player's clock duration is smaller than the effective
timeline (computed from data-start + data-hf-authored-duration on
sub-composition elements), the seek is clamped too early and sub-compositions
beyond the clock duration are invisible.

Detect this mismatch in getAdapter() and pad the root GSAP timeline to the
document duration, then force a timeline rebind so the clock updates.
2026-05-18 18:10:43 -04:00
Miguel Ángel 1a4e534d0f fix(studio): uncap seek clamp to include timeline element range
The seek function clamped to adapter.getDuration() which only knows the
root composition's authored duration. Appended timeline elements extend
beyond this range. Compute the effective max from both the adapter duration
and the store's element boundaries so scrubbing reaches the full timeline.
2026-05-18 17:28:45 -04:00
Miguel Ángel 837ffdcb5d Merge pull request #941 from heygen-com/worktree-feat+media-design-panel
feat(studio): media properties panel for video/audio elements
2026-05-18 23:28:08 +02:00
Miguel Ángel 1098f3af84 fix(studio): polish media panel UX and preserve selection on undo
- Move Timing + Media sections above Layout in the Design panel
- Remove LayerTree from Design panel (redundant with Layers tab)
- Replace Rate and Media Start with sliders matching Volume's UX
- Replace Position DetailField with SelectField to match Fit height
- Remove Poster field (not useful for HyperFrames compositions)
- Show absolute filesystem path for Source (resolves symlinks)
- Add Copy button for source path with checkmark feedback
- Preserve element selection on undo/redo instead of clearing it
2026-05-18 17:26:38 -04:00
James Russo 2fd161b943 Merge pull request #939 from heygen-com/feat/auto-size-chunk-size-when-undefined
feat(producer): auto-size chunkSize from maxParallelChunks when undefined
2026-05-18 17:25:08 -04:00
CypherPoet c088588507 docs: add canonical pipeline guide (#937)
Move the 7-step production pipeline (Capture → Design → Script → Storyboard
→ VO + Timing → Build → Validate) into its own dedicated guide so it serves
any Hyperframes project, not just website-to-video. Expand each step with
file contents, project layout, gates, and iteration patterns. Reference the
new page from website-to-video, quickstart, prompting, and launch-videos.
2026-05-18 16:18:54 -05:00
James 62c800aec3 ci: add fallow audit job (PR-scoped, new-only gate) 2026-05-18 21:18:01 +00:00
JamesandClaude Opus 4.7 a8499632c6 test(producer): preserve single-chunk-path coverage + add auto-size integration
Address PR review feedback on #939:

- Pin chunkSize=240 on the golden planDir layout test so the 1-chunk path
  through plan() stays exercised after the auto-sizer change. Assert
  chunkCount === 1 explicitly (previously just >= 1).
- Add an integration test that runs plan() with chunkSize=undefined and
  asserts the auto-sizer produces multi-chunk output end-to-end
  (chunkCount=3, encoder.gopSize=10, encoder.chunkSize=10) for the same
  30-frame fixture.
- Document the GOP/file-size trade-off on the chunkSize docstring so
  adopters who optimize for output bytes know to pin chunkSize.
- Update the resolveChunkPlan docstring formula to reference the operative
  variable (resolvedChunkSize) instead of the now-ambiguous chunkSize.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 21:09:31 +00:00
James Russo 1edea8afe4 Merge pull request #938 from heygen-com/chore/fallow-config-and-cleanup
chore: add fallow config and fix high-signal findings
2026-05-18 16:52:18 -04:00
James Russo cc7622cc42 Merge pull request #926 from heygen-com/fix/ci-chrome-pin-and-psnr-harness
fix(ci): pin chrome-headless-shell + clamp PSNR checkpoint to a valid frame
2026-05-18 16:51:29 -04:00
JamesandClaude Opus 4.7 fd3fce9955 refactor(producer): tighten resolveChunkPlan assertion + trim comments
Address self-review findings:
- assertPositiveInteger now only runs on the caller-supplied path so the
  error message names `configChunkSize` only when the caller actually
  passed one. Previously, the assertion fired against `resolvedChunkSize`
  on both paths and would have lied about the offending input.
- Drop the call-site comment that narrated the diff/history; the
  function docstring already covers the contract.
- Drop the internal-track name and date from the MIN_CHUNK_SIZE rationale
  and the test block header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:37:25 +00:00
Miguel Ángel f66c47c812 fix(studio): derive effective duration in PlayerControls instead of useEffect sync
Replace the useEffect that pushed effectiveTimelineDuration into the player
store with an inline derived selector in PlayerControls. The selector computes
Math.max(duration, maxElementEnd) directly from store state, avoiding the
effect-based sync anti-pattern entirely.
2026-05-18 16:35:59 -04:00
Miguel Ángel 96f9462e42 feat(studio): add media properties panel for video/audio elements
Adds a new Media section to the Design panel that appears when a <video>
or <audio> element is selected. Controls include volume (slider),
playback rate, media start offset, loop/muted toggles, and for video:
object-fit, object-position, poster, and has-audio-track toggle.

Extends the source patcher with an "html-attribute" operation type for
native HTML attributes (loop, muted, poster) that don't use the data-
prefix. Adds coalesceKey to attribute commits so rapid slider/scrub
edits merge into a single undo entry.
2026-05-18 16:34:41 -04:00
Miguel Ángel 43f0a2b2bb fix(studio): seek slider respects effective timeline duration with appended blocks
The seek slider read duration from the player store, which was set from the
iframe adapter's getDuration() — only aware of the root composition's authored
data-duration. Appended sub-compositions (via Blocks panel) extend the timeline
but the slider stayed capped at the original duration.

Sync effectiveTimelineDuration (which accounts for all timeline elements) into
the player store, and prevent adapter callbacks from overwriting a larger
effective duration back down to the authored value.
2026-05-18 16:32:03 -04:00
JamesandClaude Opus 4.7 e79a8faa42 feat(producer): auto-size chunkSize from maxParallelChunks when undefined
Previously, plan() defaulted chunkSize to 240 on a `?? DEFAULT_CHUNK_SIZE`
line, so a 660-frame composition with maxParallelChunks=16 ended up at 3
chunks (ceil(660/240)) regardless of the caller's fan-out intent.

When config.chunkSize is undefined, auto-size from maxParallelChunks:
  effectiveChunkSize = max(MIN_CHUNK_SIZE, ceil(totalFrames / maxParallelChunks))

MIN_CHUNK_SIZE=10 keeps per-chunk fixed overhead from swamping the
parallelism gain on tiny renders. Explicit numbers, including 240, take
precedence over the auto-sizer — no behavior change for callers that set
chunkSize explicitly.

Surfaced by the lever-1 chunk-scaling benchmark on 2026-05-17.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:29:34 +00:00
James 030a2b32ef chore: oxfmt .fallowrc.jsonc 2026-05-18 20:07:08 +00:00
James 21e68d9b50 ci: re-trigger regression on PR #926 (suspected shard-3 flake) 2026-05-18 18:58:39 +00:00
James 2087d5dab2 chore: add fallow config and fix high-signal findings
Configure fallow via .fallowrc.jsonc so its analysis reflects this repo's
real entry surface, then fix the genuine issues it found.

Fallow noise reduction (601 → 276 dead-code findings):
- Ignore docs/, test fixtures, skill test-corpora, registry/, examples/
- Declare worker entry points loaded dynamically by file path
  (pngDecodeBlitWorker.ts, shaderTransitionWorker.ts)
- Declare runtime IIFE entry (core/src/runtime/entry.ts) built outside the
  import graph by build-hyperframes-runtime-artifact.ts
- Declare bun:test files in producer + aws-lambda as test entries
- Ignore dynamically-resolved deps: tsup external (puppeteer-core, esbuild,
  giget), peer/static-file (gsap in player perf tests), workspace deps
  hoisted by bun (happy-dom, @hyperframes/*), and @fontsource/* packages
  read via readFileSync in generate-font-data.ts

Extract inline build:fonts scripts:
- packages/{cli,producer}/package.json had multi-line `node -e ...` blobs
  containing braces that fallow mis-parsed as glob alternate groups. Moved
  to dedicated build-fonts.mjs scripts.

Fix duplicate exports:
- Remove dead FileIcon alias in studio/SystemIcons.tsx (FileTreeIcons.tsx
  has the real, used one)
- Consolidate ValidationResult: drop the identical duplicate in
  gsapParser.ts; both parsers now import from core.types
- Suppress intentional namespace patterns (per-namespace ML manager
  exports; CLI per-command 'examples' convention; fileServer.ts test-only
  isPathInside which has different symlink semantics from utils/paths.ts)

Break circular dep (studio/components/editor):
- manualEditsDom.ts re-exported clearStudioPathOffset / clearStudioRotation
  / clearStudioBoxSize from manualEditsSnapshot.ts, which imports four
  helpers from manualEditsDom.ts — back-edge cycle
- Re-export moved to manualEdits.ts (the package-public barrel) where the
  rest of the snapshot re-exports already live; underlying files now form
  a clean DAG

Remove genuinely unused deps:
- studio: motion (no imports anywhere), codemirror (umbrella package; the
  @codemirror/* sub-packages are used directly)
- cli: mime-types (plus its only consumer src/utils/mime.ts, which was a
  hardcoded mime table that didn't use the package), and its now-stale
  tsup external entry

Verified: typecheck across core/cli/producer/studio is clean, oxlint
+ oxfmt pass, manualEdits.test.ts (18 tests) and core parser tests (69
tests) still pass.

Deferred follow-ups (real findings, separate PRs):
- 8 circular deps in producer/services/render/stages/ — renderOrchestrator
  ↔ captureHdr* / captureStage / extractVideosStage form a hub cycle
- ~14 unused files in producer/src/services/ that look like dead
  re-export shims to @hyperframes/engine, but aren't in the public
  exports map — need to confirm no deep-import consumers before deletion
- waveform.ts complexity hotspot
2026-05-18 18:57:21 +00:00
Miguel Ángel 27efcd0f80 chore: release v0.6.22 v0.6.22 2026-05-18 14:23:43 -04:00
Miguel Ángel 4c29b43b85 Merge pull request #935 from heygen-com/worktree-fix+canvas-zoom-improvements
fix(studio): canvas zoom improvements — zoom to cursor, reset button, border fix
2026-05-18 20:07:20 +02:00
Miguel Ángel e25bcf989a fix(studio): address PR review — split pan clamp, pin invariant, drop dead guard
- Split pan clamping: clampPreviewPan (drag/wheel-pan) stays narrow
  (Math.max(0,...) — content pins to center when smaller than viewport).
  New clampPreviewPanForZoom (Math.abs) gives the wide range only to
  cursor-anchored zoom, preventing middle-mouse drag from pushing content
  off-screen at low zoom levels.

- Pin transform-origin invariant: comment on the stage div noting that
  resolvePreviewWheelZoom cursor math depends on center-center pivot.
  New test verifies a non-center cursor keeps the same content-space
  point fixed across a zoom step.

- Remove dead Math.abs(oldScale) > 1e-6 guard — oldScale >= 0.25 always
  (clampPreviewZoomPercent floors at MIN_PREVIEW_ZOOM_PERCENT = 25).

- Skip setSettledZoom re-render when the value didn't change — uses a
  functional updater that returns the previous state object when all
  three fields match, avoiding a React re-render cascade through Player.
2026-05-18 14:04:53 -04:00
Miguel Ángel 3af7f1cba9 Merge pull request #934 from heygen-com/feat/texture-caption-any-texture
feat(registry): make caption-texture-lava work with any texture
2026-05-18 20:00:48 +02:00
Miguel Ángel 0150a0a1b4 fix(studio): canvas zoom improvements — zoom to cursor, reset button, border fix
- Zoom anchors to cursor position instead of always zooming toward center.
  The resolvePreviewWheelZoom function now accepts cursorX/cursorY (offset
  from viewport center) and uses the standard zoom-to-point formula to
  adjust pan so the content point under the cursor stays fixed.

- Add visible "Reset" button (bottom-right) showing current zoom % when
  not at fit zoom. Driven by settledZoom state that updates after the
  200ms settle debounce, so no re-renders during active zoom gestures.

- Fix border-expands-inward bug: scaleIframeToFit in the player now uses
  offsetWidth/offsetHeight instead of getBoundingClientRect. The latter
  returns values inflated by ancestor CSS zoom, causing double-scaling
  that made the iframe appear smaller than its container.

- Fix zoom HUD appearing during pan: split applyZoom (shows HUD) from
  applyPan (silent) so trackpad/middle-mouse panning no longer flashes
  the zoom percentage overlay.

- Fix stale closure performance regression: replace stageSize in effect
  dependency arrays with stageSizeRef pattern. The old deps caused wheel
  and pointer handlers to re-register on every viewport resize.

- Widen pan clamp range (Math.abs instead of Math.max(0,...)) so content
  can float within the viewport when zoomed below fit — required for
  zoom-to-cursor to work correctly at any zoom level.

Closes #900
2026-05-18 13:24:17 -04:00
Miguel Ángel 09af2f9cc1 feat(registry): rename caption-texture-lava to caption-texture with multi-texture support
Rename component to caption-texture and bundle 6 popular textures
(lava, marble, metal, wood, concrete, rock). The texture is configurable
via the texture composition variable (default: lava).

Usage: hyperframes render --variables '{"texture":"marble-012"}'

Tested: lava, marble-012, metal-046-b all render correctly with
distinct visual patterns.
2026-05-18 12:40:48 -04:00
Miguel Ángel 8163f38077 chore: release v0.6.21 v0.6.21 2026-05-18 11:29:01 -04:00
Miguel Ángel 4b6ed3197f Merge pull request #928 from heygen-com/worktree-feat+registry-us-map
feat(registry): add us-map choropleth block
2026-05-18 17:17:09 +02:00
Miguel Ángel 9f18425eae fix(lint): detect Three.js loaded via importmap or ES module import
The missing_three_script rule only checked <script src> attributes
for Three.js. Now also detects:
- importmap entries defining "three"
- ES module import/from statements referencing "three"

Closes #931
2026-05-18 11:06:51 -04:00
Miguel Ángel 15c2d7644d ci: re-trigger regression shards 2026-05-18 10:42:16 -04:00
Miguel Ángel 4bf2fa5cd1 fix: revert flattenInnerRoot, use host-level authored-id + count-based rebind
Three changes to fix regression failures without breaking baselines:

1. Revert flattenInnerRoot in producer — use the original innerHTML
   inlining that preserves the existing DOM structure. Instead, set
   data-hf-authored-id on the HOST element so the scoped proxy can
   still rewrite #id selectors for sub-composition scripts.

2. Revert compiled.html baselines to main (no DOM structure changes).

3. Use timeline-count comparison instead of poll duration to decide
   whether to call __hfForceTimelineRebind. Compare timeline count
   before vs after the poll — rebind only when new timelines appeared
   during polling. This correctly identifies async compositions
   regardless of fetch speed, while leaving sync compositions
   untouched.
2026-05-18 10:20:17 -04:00
Miguel Ángel 0ff6296d64 fix(core): remove late-bind polling from runtime — engine handles it
The setInterval-based late-bind polling in init.ts caused visual
regressions across all style-prod tests. Even with the sawMissing
guard, the mere presence of the interval registration altered
event loop timing enough to shift rendered frames.

The engine's pollSubCompositionTimelines + conditional
__hfForceTimelineRebind already handles async timeline detection
for renders. The runtime only needs to expose the rebind hook —
it shouldn't poll on its own.

For studio preview of async compositions, the engine's rebind
call (via __hfForceTimelineRebind) is the correct mechanism.
2026-05-18 05:50:08 -04:00
Miguel Ángel d10b2f4ded fix(core): only rebind timelines when late arrivals were actually detected
The late-bind polling was unconditionally rebinding on its first
check even when all timelines were already present, causing visual
regressions across style-prod tests. Now tracks sawMissing flag —
only rebinds if the poll previously detected missing timelines that
subsequently appeared. Compositions with synchronous timeline
registration exit the poll immediately with no side effects.
2026-05-18 05:24:33 -04:00
Miguel Ángel 04c35ce24c fix: regression fixes — conditional rebind + updated compilation baselines
1. Only call __hfForceTimelineRebind() when the timeline poll actually
   had to wait (pollDuration > 2 intervals). For compositions with
   synchronous timeline registration, the rebind was unnecessary and
   shifted render timing, causing PSNR regressions in chat and
   gsap-letters-render-compat.

2. Regenerate compiled.html baselines for missing-host-comp-id and
   overlay-montage-prod to match the new flattenInnerRoot behavior
   (data-composition-id stripped from inlined inner roots, replaced
   with data-hf-authored-id).

3. Add late-bind polling to runtime init.ts — after external
   compositions load, poll for 5s to detect async timelines that
   register after initial binding (e.g. from fetch callbacks).
2026-05-18 03:46:58 -04:00