Skill Tracer
Skills › HyperFrames › remotion-to-hyperframes › escape-hatch.md

escape-hatch.md

115 lines · skills/remotion-to-hyperframes/references/escape-hatch.md at a595755

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# When to bow out: the runtime interop pattern
2
3Some Remotion compositions can't be translated cleanly. The skill should
4recognize them upfront and recommend the **runtime interop pattern** from
5[PR #214](https://github.com/heygen-com/hyperframes/pull/214) instead of
6producing broken HTML.
7

When to bow out: the runtime interop pattern

Some Remotion compositions can't be translated cleanly. The skill should recognize them upfront and recommend the runtime interop pattern from PR #214 instead of producing broken HTML.

Raw lines 1 to 7
8## When to recommend interop
9
10Run `scripts/lint_source.py` first. If it returns any blocker, recommend
11interop. The blockers are:
13| Rule | What it catches |
14| --------------------------- | -------------------------------------------------------------- |
15| `r2hf/use-state` | useState driving animation |
16| `r2hf/use-reducer` | useReducer driving animation |
17| `r2hf/use-effect-deps` | useEffect/useLayoutEffect with non-empty deps (side effects) |
18| `r2hf/async-metadata` | calculateMetadata returns a Promise |
19| `r2hf/third-party-react-ui` | Imports from MUI, Chakra, Mantine, antd, shadcn, Radix, NextUI |
21Each of these breaks the seek-driven, deterministic-frame model that HF
22relies on. Translating them produces silently-wrong output.

When to recommend interop

Run scripts/lint_source.py first. If it returns any blocker, recommend interop. The blockers are:

RuleWhat it catches
r2hf/use-stateuseState driving animation
r2hf/use-reduceruseReducer driving animation
r2hf/use-effect-depsuseEffect/useLayoutEffect with non-empty deps (side effects)
r2hf/async-metadatacalculateMetadata returns a Promise
r2hf/third-party-react-uiImports from MUI, Chakra, Mantine, antd, shadcn, Radix, NextUI

Each of these breaks the seek-driven, deterministic-frame model that HF relies on. Translating them produces silently-wrong output.

Raw lines 8 to 23
Runs code scripts/lint_source.py :10
24## What the interop pattern actually does
26Per [PR #214](https://github.com/heygen-com/hyperframes/pull/214), the
27runtime adapter:
291. Bundles the user's Remotion code with React + `@remotion/player` via esbuild.
302. Mounts a Remotion `<Player>` inside an HF composition's HTML.
313. Pauses the player on mount.
324. Registers the player on `window.__hfRemotion` with `seekTo(frame)`,
33 `pause()`, `durationInFrames`, `fps`.
345. HF's render loop seeks the player frame-by-frame via `seekTo(frame)`.
36Result: Remotion's React tree renders at HF's deterministic frame ticks.
37Custom hooks, useState, useEffect, MUI components — all work because
38Remotion's React reconciler is doing the rendering.

What the interop pattern actually does

Per PR #214, the runtime adapter:

  1. Bundles the user's Remotion code with React + @remotion/player via esbuild.
  2. Mounts a Remotion <Player> inside an HF composition's HTML.
  3. Pauses the player on mount.
  4. Registers the player on window.__hfRemotion with seekTo(frame), pause(), durationInFrames, fps.
  5. HF's render loop seeks the player frame-by-frame via seekTo(frame).

Result: Remotion's React tree renders at HF's deterministic frame ticks. Custom hooks, useState, useEffect, MUI components — all work because Remotion's React reconciler is doing the rendering.

Raw lines 24 to 39
40## The recommendation message
42When the skill detects a blocker, output something like:
44> The Remotion source uses `useState` (and others), which can't be
45> translated to HF's seek-driven HTML model. The recommended path is the
46> **runtime interop pattern**: bundle your Remotion code with `@remotion/player`
47> and let HF drive it frame-by-frame.
49> See https://github.com/heygen-com/hyperframes/pull/214 for the full
50> implementation. Quick summary:
52> 1. Bundle `entry.tsx` with esbuild: `npx esbuild entry.tsx --bundle --outfile=dist/bundle.js --format=iife --jsx=automatic`
53> 2. Mount the Player and register on `window.__hfRemotion`:
55> ```tsx
56> const playerRef = useRef<PlayerRef>(null);
57> useEffect(() => {
58> playerRef.current?.pause();
59> window.__hfRemotion = window.__hfRemotion || [];
60> window.__hfRemotion.push({
61> seekTo: (f) => playerRef.current?.seekTo(f),
62> pause: () => playerRef.current?.pause(),
63> durationInFrames,
64> fps,
65> });
66> }, []);
67> ```
69> 3. Reference the bundle from your HF `index.html` and render normally:
70> `<script src="dist/bundle.js"></script>`

The recommendation message

When the skill detects a blocker, output something like:

The Remotion source uses useState (and others), which can't be translated to HF's seek-driven HTML model. The recommended path is the runtime interop pattern: bundle your Remotion code with @remotion/player and let HF drive it frame-by-frame. See https://github.com/heygen-com/hyperframes/pull/214 for the full implementation. Quick summary: 1. Bundle entry.tsx with esbuild: npx esbuild entry.tsx --bundle --outfile=dist/bundle.js --format=iife --jsx=automatic 2. Mount the Player and register on window.__hfRemotion: ``tsx const playerRef = useRef<PlayerRef>(null); useEffect(() => { playerRef.current?.pause(); window.__hfRemotion = window.__hfRemotion || []; window.__hfRemotion.push({ seekTo: (f) => playerRef.current?.seekTo(f), pause: () => playerRef.current?.pause(), durationInFrames, fps, }); }, []); ` 3. Reference the bundle from your HF index.html and render normally: <script src="dist/bundle.js"></script>`
Raw lines 40 to 71
72## The lint output already includes recommendations
74`lint_source.py` emits a `recommendation` field per finding. Surface those
75verbatim — they're tuned per blocker rule:
77```json
79 "rule": "r2hf/use-state",
80 "message": "useState detected — Remotion compositions that drive animation via React state are not deterministic frame-capture targets in HyperFrames",
81 "recommendation": "Use the runtime interop pattern from PR #214 instead of attempting a translation"
83```

The lint output already includes recommendations

lint_source.py emits a recommendation field per finding. Surface those verbatim — they're tuned per blocker rule:

{
  "rule": "r2hf/use-state",
  "message": "useState detected — Remotion compositions that drive animation via React state are not deterministic frame-capture targets in HyperFrames",
  "recommendation": "Use the runtime interop pattern from PR #214 instead of attempting a translation"
}
Raw lines 72 to 84
85## When NOT to bow out: warnings only
87Some patterns produce warnings, not blockers — translate after dropping
88the wrappers:
90| Rule | Action |
91| ------------------------- | ------------------------------------------------------------------- |
92| `r2hf/lambda-import` | drop the `@remotion/lambda` config; HF runs single-machine, log gap |
93| `r2hf/delay-render` | drop the call; HF handles asset readiness |
94| `r2hf/use-callback` | drop the wrapper, inline the function |
95| `r2hf/use-memo` | drop the wrapper, compute inline |
96| `r2hf/custom-hook` (pure) | inline the hook body if it's a derivation of `useCurrentFrame` |
97| `r2hf/static-file` | replace `staticFile("x")` with `"assets/x"` |
98| `r2hf/interpolate-colors` | translate to GSAP color tween (see [timing.md](timing.md)) |
100These are documented in T4 cases 05–07.
102`r2hf/lambda-import` is a warning — not a blocker — because Lambda
103configuration is orthogonal to the rendered composition. Translating an
104otherwise-clean Remotion comp shouldn't fail just because the author also
105configured AWS Lambda for distributed rendering. The skill drops the
106`@remotion/lambda` imports and `renderMediaOnLambda(...)` calls in step 3
107(Generate) and writes a `TRANSLATION_NOTES.md` entry so the user knows to
108set up HF rendering separately.

When NOT to bow out: warnings only

Some patterns produce warnings, not blockers — translate after dropping the wrappers:

RuleAction
r2hf/lambda-importdrop the @remotion/lambda config; HF runs single-machine, log gap
r2hf/delay-renderdrop the call; HF handles asset readiness
r2hf/use-callbackdrop the wrapper, inline the function
r2hf/use-memodrop the wrapper, compute inline
r2hf/custom-hook (pure)inline the hook body if it's a derivation of useCurrentFrame
r2hf/static-filereplace staticFile("x") with "assets/x"
r2hf/interpolate-colorstranslate to GSAP color tween (see timing.md)

These are documented in T4 cases 05–07.

r2hf/lambda-import is a warning — not a blocker — because Lambda configuration is orthogonal to the rendered composition. Translating an otherwise-clean Remotion comp shouldn't fail just because the author also configured AWS Lambda for distributed rendering. The skill drops the @remotion/lambda imports and renderMediaOnLambda(...) calls in step 3 (Generate) and writes a TRANSLATION_NOTES.md entry so the user knows to set up HF rendering separately.

Raw lines 85 to 109
110## When the source has BOTH blockers AND warnings
112Bow out. The presence of a single blocker means the skill shouldn't
113attempt translation — even if the rest of the composition is clean.
114The user should use interop for the whole thing OR refactor the
115blocker patterns out of their Remotion source first.

When the source has BOTH blockers AND warnings

Bow out. The presence of a single blocker means the skill shouldn't attempt translation — even if the rest of the composition is clean. The user should use interop for the whole thing OR refactor the blocker patterns out of their Remotion source first.

Raw lines 110 to 115