remotion-composition-builder
The hands. Reads everything else, writes the TSX.
Triggers
- Wave 4 of a
remotion-orchestratorplan. - User asks to "build a composition for {topic}" with brand + storyboard already known.
Inputs
All consumed from prior skill outputs in the same job:
BrandConfig(fromsrc/brands/{slug}.ts) — runtime + behaviour: voice, voiceover, motion, audience, channel, forbiddenWords{slug}.design.md(loaded vialoadDesign(slug)fromsrc/brands/loadDesign.ts) — visual tokens: colour, typography, spacing, components, layout, elevation- Storyboard JSON (from
remotion-screen-storyteller) - Layout spec (from
remotion-designer) — references design.md tokens by name - Motion block (part of
BrandConfig) - Channel spec / aspect ratio (from
remotion-marketing-strategist)
See src/brands/CONTRACT.md for the full ownership boundary between BrandConfig and design.md.
Method
- Locate composition — does
src/compositions/{Name}.tsxexist?- If yes → patch in place (preserve existing API).
- If no → create new file using
Explainer.tsxas the template.
- Wire imports —
import { brands } from '../brands'for runtime,import { loadDesign, colour, spacing, componentRecipe, typography } from '../design'for visual tokens, motion helpers from../motion. - Define schema —
z.objectfor input props matching the Storyboard shape. - Compose scenes — one
<Sequence>per storyboard scene. Each scene component readscfg = brands[props.brand](for motion/voice/voiceover) ANDtokens = loadDesign(props.brand)(for colour/typography/spacing/component recipes), then composes them. UsecomponentRecipe(tokens, 'cta-primary')to get a fully-resolved CSS-ready object instead of inlining hexes. - Audio — wire
<Audio src={staticFile(scene.voiceoverAudioPath)} />per scene if path is set. - Register in Root — add or update
<Composition id="{Name}" ... />insrc/Root.tsx.durationInFrames = sum(scene.durationSec) * fps. - File hygiene — composition file ≤500 lines. Extract scenes to
src/compositions/{Name}/scenes/*.tsxif it grows past that. - Type check — run
npx tsc --noEmit. On red, fix and retry once.
Output
Edited files:
src/compositions/{Name}.tsx(or scene sub-files)src/Root.tsx(registry entry)
Status report to remotion-orchestrator:
{ "composition": "Explainer", "filesChanged": [...], "tscPassed": true, "ready": true }
Boundaries
- Never inline brand values (hex codes, font names, spacing literals) — always read from
tokens(visual) orcfg(runtime). SeeCONTRACT.mdfor which side owns what. - Never set
durationInFrameswithout summing storyboard scene durations × fps. - Never
staticFile()an asset path that doesn't exist — fail loud at build, not silent at render. - Never bypass
signatureEntry()for entry motion — keeps motion-language consistent. - Never duplicate a token across
BrandConfig.tsand{slug}.design.md. If the value lives in.design.md, the.tsmust not redeclare it.
Reused
src/compositions/Explainer.tsx— canonical templatesrc/motion/index.ts—signatureEntry,brandFadeIn,staggerStartsrc/colour/index.ts—readableOn,brandGradient,contrastsrc/design/index.ts—loadDesign,colour,spacing,typography,componentRecipe,resolveTokensrc/brands/loadDesign.ts— typed loader for{slug}.design.mdsrc/brands/CONTRACT.md—BrandConfig↔design.mdownership boundary
Hands off to
remotion-render-pipeline to actually render.