Skip to content

feat(core): bridge preserved film HTML to native capture - #5131

Merged
jrusso1020 merged 11 commits into
mainfrom
codex/film-runtime-bridge
Oct 7, 2026
Merged

jrusso1020 merged 11 commits into
mainfrom
codex/film-runtime-bridge

Conversation

@jrusso1020

@jrusso1020 jrusso1020 commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

What

Add window.__hyperframes.createFilmBridge to drive preserved film-runner HTML through its current appifact-film: protocol. The wrapper can keep the runner, scene modules and assets unchanged while HyperFrames owns time and screenshot capture.

Why

Current Claude Motion previews already expose a sandboxed runner with explicit frame requests. Supporting that protocol avoids requiring a new export format or translating animation code into GSAP.

Related work

Depends on #5130; merge it first. Second of three PRs; #5132 adds bounded scene timing. Somansh owns MCP extraction, asset packaging and import/export.

How

Install the message listener before setting srcdoc, retain the opaque allow-scripts sandbox, and validate both source window and origin. Load after hello, wait for ready, then await matching frame sequence IDs. Bound startup/frame waits and reject fatal errors or disposal; individual frame errors can recover on later seeks. No separate rendering service or autoplay clock is introduced.

Test plan

  • Unit tests added/updated: payload/handshake, spoofed origins/sources, premature ready, stale sequence IDs, frame recovery, fatal errors, timeouts, disposal and public runtime API.
  • Manual testing performed: partner HTML visual/export acceptance remains pending MCP packaging.
  • Documentation updated with importer handoff and current protocol boundaries.
  • Comments follow CONTRIBUTING.md.

Core build/typecheck, changed-file lint/format, 4,240 core tests passed (--maxWorkers=4 --testTimeout=30000). Focused bridge/runtime tests rerun after simplifying the message handlers. The original partner HTML is not committed. Audio/video extraction and manual editing inside the iframe are outside this slice.

Complexity report: highest new message handler is CC 8; no new function exceeds 10.

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed at 30f6c08aa, focused on the opaque iframe protocol. The core checks are right:

  • the sandbox refuses allow-same-origin;
  • event.source === iframe.contentWindow and origin === "null" are both required;
  • frame replies are matched on seq.

Two should-fixes:

1. A reloaded runner is never loaded again (filmBridge.ts:66-72).

  • loaded stays true for the bridge's lifetime. If the film document navigates or reloads (film code calling location.reload(), or a link inside the sandbox), the new document's contentWindow is the same browsing context, so its hello passes the source/origin checks. Because !loaded is false, it never gets load.
  • The next render posts frame to an unloaded runner and fails after the 15 s timeout.
  • (Inference, not browser-tested) A reply already queued from the old document for the current seq could also be accepted.
  • Fix: treat a second hello as a new document generation. Fail the bridge fast, or reject pending and redo the handshake, rather than waiting on the timeout.

2. A send failure stalls instead of failing (filmBridge.ts:69, :100).

  • postMessage({ ...load, ... }) throws DataCloneError for any non-cloneable value, and load is typed Record<string, unknown>.
  • The throw escapes the message handler with loaded already true, so ready hangs until the 20 s startup timeout.
  • Fix: wrap both postMessage calls and route a throw to fail(). The same applies to a null contentWindow at render time, which currently waits out the 15 s.

— Jerrai

@jrusso1020

Copy link
Copy Markdown
Collaborator Author

Both findings are valid and fixed in d3e7bcb; parent fixes are carried forward without rewriting existing commits.

  • A second runner hello now fails immediately with a reload error and rejects any pending frame. Recreating the bridge is required because the existing protocol cannot distinguish old-document acknowledgements from the new document using the same WindowProxy. Late ready/frame replies cannot revive the failed bridge.
  • Load and frame postMessage calls now share a guarded send path. Clone/send exceptions and missing contentWindow fail immediately and clear startup/frame timers.

Regression tests cover reload before readiness and during a pending frame, old acknowledgements, DataCloneError on load, frame-send exceptions, and missing runner windows. Focused core tests: 55 passed. Final-stack validation: 4,252 core, 591 SDK, and 51 real-browser tests passed, plus core build/typecheck and commit hooks.

Actual partner HTML preview/export acceptance remains unverified under the previously reported browser policy block.

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Re-reviewed at d3e7bcb52. Both findings are fixed.

  • Reload: a second hello fails the bridge, rejects ready and any pending frame, and clears both timers. Later messages can't revive it. Recreating the bridge on reload is the right call, since old-document replies can't be told apart.
  • Failed sends: both postMessage sites go through the guarded send, so a clone error or a missing contentWindow fails immediately. All three cases have tests.

— Jerrai

miguel-heygen
miguel-heygen previously approved these changes Oct 6, 2026

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

Reviewed at d3e7bcb52, focused on the message protocol and its failure paths.

What holds up:

  • allow-same-origin is refused at construction (filmBridge.ts:16-19).
  • Both event.source and the opaque "null" origin are checked (:91), and acknowledgements match on seq, so a spoofed or stale reply cannot release capture.
  • The new send() wrapper turns a missing window or a DataCloneError into an immediate failure, and a second hello now fails fast instead of waiting out the deadline.

One minor item, not blocking:

Minor: one slow frame ends the bridge for the life of the page.

  • The frame deadline calls fail() (:110), which sets failure (:38). From then on receive drops every message (:91) and every render throws (:105).
  • frame-sources.md says a later successful seek recovers from a frame failure, and imported-film-html.md says a frame-error recovers. A timeout is the one frame failure that never does.
  • Export is unaffected: the barrier fails that capture either way. Preview is where it shows. One frame over 15 s (a cold asset fetch, or a runner that waits on requestAnimationFrame while the editor tab is in the background) leaves the scene frozen until a full reload, with nothing in the UI saying why.
  • Fix: on a frame timeout, reject only that request and clear pending, the way frame-error does at :62. Keep startup failures terminal. A late reply for the old seq is already ignored by the sequence check.

CI at this head: 28 passing. regression and Player perf were cancelled at this sha, not failed; rerun them before merge.

Verdict: APPROVE
Reasoning: sender, origin and sequence validation are correct, and every send and startup failure now fails promptly. The open item is a recovery path in preview, not wrong output.

jrusso1020 and others added 5 commits October 6, 2026 17:43
A composition that follows the frame-source contract registers no GSAP
timeline, so lint reported missing_timeline_registry and every render
waited 45 s for timeline registration (17.6 s -> 1 min 47.9 s for the
14 s imported film sample). Document the existing data-no-timeline
opt-out for the host and a timeline-less root.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Imported film wrappers have no GSAP timeline. Without data-no-timeline
on the scene hosts and root, lint fails with missing_timeline_registry
and each render waits 45 s for timeline registration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approved at c4b8763c7. The parent merge carries exactly the #5130 delta (init.ts +3, the browser test, and the frame-sources doc line), plus one doc line in imported-film-html.md requiring data-no-timeline on the film host. Nothing else changed since my last approval.

The regression, Perf and Preview parity failures here were cancelled runs ("Change detection did not succeed"), not test failures. I have re-run them.

— Jerrai

Base automatically changed from codex/external-renderer-bridge to main October 7, 2026 02:18
@jrusso1020
jrusso1020 dismissed stale reviews from jerrai-bot-heygen and miguel-heygen October 7, 2026 02:18

The base branch was changed.

@mintlify

mintlify Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Oct 7, 2026, 2:28 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approved again at 5541b4383, after the retarget to main.

I checked the new diff against main, file by file, against this PR's own delta at my last approval (29a5c02b4...c4b8763c7). It is the same 7 files, and every added and removed line is identical:

  • filmBridge.ts: 123 changed lines.
  • filmBridge.test.ts: 202.
  • imported-film-html.md: 32.
  • entry.ts: 3.
  • entry.test.ts, window.d.ts and docs.json: 1 each.

So the merge of main kept the film bridge exactly as it was reviewed, and nothing else entered the diff.

— Jerrai

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown

Edit accuracy: accurate 2059 (base branch 2059), smooth 1664 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (0)

@jrusso1020
jrusso1020 added this pull request to the merge queue Oct 7, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 7, 2026

@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-stamp at 5541b4383, now based on main after #5130 landed. Since my review at d3e7bcb52, this PR's own diff changed only in imported-film-html.md (hosts and timeline-less roots take data-no-timeline). The transport-redraw fix arrived through #5130 and is on main. Bridge code is unchanged, so the earlier findings and the open minor (a frame timeout is terminal in preview) stand as written.

CI: 85 passing, 1 red. The red is Studio: timeline viewport gate: interaction p95 was 71.7 ms on the first attempt and 58.9 ms on the second, against a 58.3 ms budget. Main failed the same gate on the #5130 merge commit a17d0bfc0 and passed it on the next two commits. This PR touches no Studio code, so I am not counting it against the change; a rerun should clear it.

Verdict: APPROVE
Reasoning: the bridge is unchanged since the reviewed head and the delta is documentation.

@jrusso1020
jrusso1020 added this pull request to the merge queue Oct 7, 2026
Merged via the queue into main with commit 891a18a Oct 7, 2026
93 of 94 checks passed
@jrusso1020
jrusso1020 deleted the codex/film-runtime-bridge branch October 7, 2026 04:14

This branch was successfully deployed

1 active deployment
staging - docs — 5541b438 Deployed Oct 7, 2026 by mintlify[bot]
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.

3 participants