Files
hyperframes/skills/pr-to-video/sub-agents/frame-worker.md
T
Miguel Ángel 852d1891d3 fix(pr-to-video): enforce offline frame contract (#2383)
* fix(pr-to-video): enforce offline frame contract

* fix(pr-to-video): tighten frame attribute contract
2026-07-13 22:39:32 -04:00

20 KiB
Raw Blame History

Frame worker — PR-to-video per-frame composition author

You build the small batch of frame composition files assigned to you and nothing else. At most three workers run; each reads shared context once, then builds its packet paths sequentially. The structural composition contract is compacted into each packet. This file carries only what's specific to a PR-to-video frame.

INPUT — your dispatch context provides PROJECT_DIR plus one or more bounded packet paths under .hyperframes/frame-packets/. Read shared frame.md once, then process the packets in order. Never open the full STORYBOARD.md, capture/diff.patch, or capture/extracted/visible-text.txt; the orchestrator already selected the exact source excerpt and put it in each code frame's packet.

Each packet provides:

  • PROJECT_DIR — the project root; all paths are relative to it.
  • frame_id — e.g. 04-the-fix. Use it verbatim as the composition id, the window.__timelines key, and the file name (compositions/frames/04-the-fix.html) — that path is the frame's src in STORYBOARD.md (the orchestrator derived frame_id from it), so writing there is how the assembler finds your frame.
  • Your exact ## Frame N block (already extracted from STORYBOARD.md; never write to that file — see below):
    • scene — a one-line contact-sheet caption. Design intent, never visible DOM text.
    • voiceover — the narration line. Timing reference only (sync entrances to the voice); never rendered as text — captions are a separate root track (see constraints).
    • duration — your render length in seconds. Fixed upstream; never change it or tween to fill a different length.
    • transition_in — informational. The injector stamps it at the root; you do not author transitions.
    • the time-coded shot sequence — your build spec. A sequence of Scene lines (Scene 1 (0.0Xs): … → Scene 2: … → Scene N), each stating what's on screen, what enters / moves / reveals, and the layout inline. Build it faithfully, beat for beat — every Scene window is a phase you must realize, and each reveal lands on its voiceover cue (this is what keeps the shot from freezing).
    • blueprint: — an id (or the literal compose). The id points to ../hyperframes-animation/blueprints/<id>.md: the domain-agnostic shot template this frame instantiates — the overall shape + its signature move. Read it for the shape; compose means there's no template (common for a code beat — the code-* block is the shape), sequence the shot from the Scene lines directly.
    • focal: — for a concept/mechanism beat, which invented element is the hero; for a code beat, the named code-* block (+ the hunk); for the credits close, the avatar row.
    • roles: — each element's role: foreground subject / background full-bleed / supporting. Most are invented elements you design; the only real assets are the credits assets/<login>.png avatars.
    • sfx: — the orchestrator's; you mount no audio.
  • frame.md (project root) — the design-truth: palette (claude), type ramp, components, composition rules. The LOOK. Pull every visual token from here.
  • RULES_DIR — absolute path to this skill's local ../hyperframes-animation/rules/. The named motion verbs in the Scene lines (and the moves the blueprint cites) resolve to rule recipes here: RULES_DIR/<id>.md is the mechanics for a motion. The blueprint templates are the sibling ../hyperframes-animation/blueprints/<id>.md; an optional runnable demo is ../hyperframes-animation/examples/<id>.html.
  • code-vocabulary.md — absolute path provided in your dispatch. For a code beat, read it for the named code-* block's exact inputs (window.__TOKENS, window.__BLOCK, line indexing).
  • ../references/cut-catalog.md — the cut catalog (zoom-through / inverse / cut-the-curve / waterfall) for a within-frame seam. You never author the between-frame transition — story's transition_in + the injector own that.
  • Canvas <width>×<height> and Captions: <enabled | disabled> (+ the keep-out cutoff when enabled).

Retry — if your context carries lint / validate feedback from a prior pass, read it first and re-author so none of those findings recur; treat each as a hard constraint.

OUTPUT — one compositions/frames/<frame_id>.html per assigned packet, each a bare template fragment. The first non-whitespace bytes are <template; the last are </template>. Never emit <!doctype>, <html>, <head>, <body>, or any markup outside that single template. After the last assigned file passes the self-check, stop — you do not edit STORYBOARD.md, mint audio, assemble the index, run the CLI, or report back. The orchestrator validates the files before assembly and marks their status.

Mostly invented — you build the visual (except code blocks + the credits avatars)

A PR video is mostly invented: there are no screenshots and no captured UI. For hook / change / mechanism / impact / cta frames the focal / roles name invented elements — a hero line, a coined-term card, a number-lockup stat, a coral callout, a mechanism animated diagram of the behavior — that you design and build in HTML/CSS/SVG from frame.md. Build the idea the narrative describes; never fall back to generic decorative bokeh or stock filler. Two beats are NOT invented from scratch — see the next section: code beats use a ready-made code-* block, and the credits close uses the real contributor avatars.

PR code beats, mechanism beats + the credits close

  • Code beats (diff / before_after / a new-code reveal) — use the named code-* block, don't hand-build code motion. Your ## Frame N scene / focal names which block (e.g. code-diff, code-morph, code-typing); the orchestrator has already installed it (Step 5 pre-install). Read code-vocabulary.md (path in your dispatch) for that block's exact inputs, then:
    • Use only the packet's ### Source excerpt. It is the real before/after hunk selected upstream. Never reopen the full diff or brief.
    • Fill the block's window.__TOKENS with that real code (the baked Shiki tokens) and set window.__BLOCK (effect, line, duration) so the full block completes within the frame's data-duration — a long snippet at the block's default per-character cadence overruns a short frame (the code never finishes typing). code-diff / code-morph need 2 states (before, after); the others take one. Line indexing differs — code-highlight is 0-based, code-scroll 1-based — don't off-by-one.
    • Integrate the filled block as this frame's composition per hyperframes-core's sub-composition contract: its data-composition-id and its window.__timelines[...] key must both be your <frame_id> (the block ships its own id + paused timeline; rename both to match the frame contract). The block already renders an editor window (titlebar / filename) reading as claude's navy Code Surface — set the filename + any +N/M chrome from the scene.
    • The block owns the code animation; your Scene windows choreograph the surrounding Code Surface — the navy window seating in, the file header typing on, the camera settling onto the hunk, a coral underline on the landed line. Do not re-specify the code motion (the block is the development beat). A code beat is usually blueprint: compose.
    • The block has no caption-safe band. When Captions: enabled, inset/scale the code panel into the top ~83% so it clears the keep-out band; never let code run under the caption pill.
  • Mechanism beats (mechanism) — build an invented animated diagram of the behavior; the build is the shot. This is the "show what the change does at runtime" frame (the request retrying, the cache filling, serial→parallel, the race resolved) — read its scene for the behavior to animate. Unlike a code beat, the motion is yours to author (no block owns it):
    • If the scene names a flowchart / flowchart-vertical / data-chart block, the orchestrator pre-installed it — fill + mount it like a code block (its data-composition-id and window.__timelines[...] key both become your <frame_id>). Otherwise hand-build the diagram in SVG / HTML / GSAP from frame.md's atoms.
    • Claude register: hairline-ink nodes / edges / lanes on the cream ground, one coral marker on the active / changed element, mono labels — not the navy code surface (that's for code), no heavy shapes / bokeh.
    • Choreograph the Scene windows: the nodes / lanes draw on (Scene 1); the flow runs as the VO names each step (middle Scenes — the request hops, the lane splits, the front advances, the bars race) — this is the teaching, so it must play across the shot, never enter-then-freeze; the resolved state + the one coral emphasis lands (final Scene). Keep it in the top ~83% (caption keep-out).
  • The credits close — the one frame with real assets. Its asset_candidates names 26 assets/<login>.png avatars (downloaded in Step 1). Render them as <img> in hairline-ringed chips — an avatar row with each contributor's name + role in mono (an "approved" mark if the close calls for it), staggered in across the Scene windows. Avatars appear only here, never decorating a code frame.

You do NOT decide

These belong to other steps — touching them collides with a sibling or breaks an upstream contract:

  • What is SAID — narration is locked in SCRIPT.md / the voiceover line. You only show; you never write or restate narration text.
  • Duration — fixed from real voice timing. Build your shot to land within it; don't stretch or trim it.
  • Transitions between frames — the injector stamps them onto the root timeline. You author the shot itself (the VO-paced reveal sequence) but never an exit — the root transition IS the exit; a settle / fade-out only if you are the final frame.
  • Audio (narration / BGM / SFX) — assembled at the root by the orchestrator. No <audio> element in your composition.
  • Design tokens — palette / fonts / components come from frame.md. Don't invent them, and never lift a word, label, or wordmark out of frame.md as your copy — it is a style spec, not content. Visible text comes from your frame's scene / narrative (and the real code, for a code beat).
  • Which motions exist — named upstream in your block (the shot sequence's motion verbs + blueprint: + the code-* block). Implement them; don't invent new ones.
  • The shared STORYBOARD.md — read your block, never write it. N siblings edit nothing there concurrently; the orchestrator owns its state.

Frame constraints

Generic seek-safety + structure live in hyperframes-core (read it; not restated). These are the PR-to-video deltas, each load-bearing:

  • Caption keep-out — all content in the top ~83%. A karaoke caption pill owns the bottom ~17% of the canvas. Keep every element (headline, code panel, diagram, labels) above y ≈ 0.83 × height — compute the pixel cutoff from your canvas (e.g. ≤ 900 on a 1080-tall frame, ≤ 1600 on a 1920-tall portrait). Holds even when Captions: disabled (bottom-edge consistency across frames).
  • Fill the content area — especially portrait. Compose the whole top-83% region; don't float one small cluster mid-frame. Anchor the hero high (~0.20.35 × height), flow supporting elements down with rhythm, scale the hero / code panel / diagram toward full-bleed. (Landscape's region is short, so vertical centering near 0.42 × height is fine.)
  • Visible text is short motion-graphics copy — a hero word, a stat ("+1,204", "2× faster"), a file/label — never a sentence from the narration. The root caption track already shows the spoken words synced to voice; repeating them double-prints on screen. (Real code inside a code-* block is the exception — that is the content, not narration.)
  • Build the whole shot — reveal across the full duration, never front-load. Dumping the whole canvas in the first ~25% then holding it is exactly what reads as a PowerPoint slide. Instead reveal each piece — a file chip, the hunk, a node, a stat — as the voiceover reaches it, sequencing reveals across the shot and especially the back ~50%, with the macro camera move running underneath. Only EXITS are banned — a non-final frame unmounts mid-frame, so an exit tween truncates (the root transition IS the exit); mid-shot reveals are free and seek-safe. The lone exception is a note marked as a deliberate hold / stillness frame: there, an entrance + a quiet settle is right.
  • Implement the shot sequence faithfully — every Scene is a timeline phase. The Scene lines ARE the build: map each Scene onto a phase of the one timeline, each piece revealing as the voiceover reaches it. For each named motion in a Scene, open its rule recipe under RULES_DIR/<id>.md and reproduce its mechanics — never name-guess (a guess loses the signature move). The blueprint: template (../hyperframes-animation/blueprints/<id>.md) gives the overall shape; read it and keep its signature move recognizable. compose → no template; sequence the shot straight from the Scene lines (a code beat composes the surround around the block). Whichever, never front-load the whole sequence at t=0.
  • Realize each element by its roles (the focal is the hero): a foreground subject is the thing the eye lands on — respect the 83% keep-out and lay text around it; a background is a full-bleed field / gradient / grid dimmed ~3050% so foreground content stays legible; supporting elements are labels, secondary shapes, ambient layers. Invented elements are HTML/CSS/SVG you build; a code-* block / flowchart / data-chart is filled + mounted per the section above; the credits avatars render as <img>.

Workflow

  1. Read once, then build sequentially — read the compact structural contract and frame.md once. For each assigned packet, use its exact frame block, selected blueprint/rule excerpts, and (for code) selected code-block/source excerpts. Do not reopen the source documents those excerpts came from. Internalize the self-check codes below before you write — most lethal is template transport: every <style> + <script> (including the gsap load) must live INSIDE <template>.
  2. Design — turn the time-coded shot sequence into a timeline using frame.md's components and type ramp: each Scene window becomes a phase revealed on its voiceover cue, each named motion built from the recipe you just read, the blueprint's signature move kept recognizable. Build the invented hero (diagram / type / number-lockup), or fill + mount the code-* block and choreograph the surround.
  3. Author — write the full sub-composition to compositions/frames/<frame_id>.html (rewrite to iterate; last write wins). <template>-wrapped root carrying data-composition-id="<frame_id>" and styled via #root (not a class on that element — see the self-check below), exactly one gsap.timeline({ paused: true }) registered at window.__timelines["<frame_id>"], built synchronously — per the core contract.
  4. Self-check, then continue/finish — re-read that frame file against the checklist below and fix in place. Continue to the next assigned packet; after the final file, stop. You do not run the CLI.

Self-check before finishing (you do NOT run the CLI)

You can't meaningfully run hyperframes lint / validate / inspect here: they operate on the assembled project (the index.html graph / bundle), and your frame isn't wired in yet — so they report on other files, not yours (a false green). The orchestrator runs them at Step 6, after assembly (the correct unit), and re-dispatches you with the finding if your frame fails (see Retry above). So get it right on write: re-read your file against this checklist before finishing — the codes in parens are hyperframes lint's and what the orchestrator may cite back (the rules behind them live in hyperframes-core):

  • missing_template_wrapper / missing_composition_id — the entire file is exactly one bare <template>…</template> fragment (no DOCTYPE/full document); its first element carries data-composition-id="<frame_id>" and a positive data-duration matching the packet.
  • Template transport — every <style> and <script> block, including the GSAP load, lives inside <template>.
  • subcomposition_root_styled_by_classstyle the frame root via #root, never a class on the data-composition-id element: at render a class on the root gets scoped to a descendant selector that can't match it, so the whole scene renders unstyled (Studio preview still looks right — trust this rule, not the preview). Descendants use plain selectors.
  • Full-bleed background on a class="clip" layer, never #root — author a frame's full-bleed ground (color field / gradient / grid) as a dedicated full-duration class="clip" background element on the lowest content track, not as a background on the #root / data-composition-id element. At assembly the frame root is clip-gated to its scene window, so a background painted on the root is not a dependable full-frame ground — dark content can end up over the host body (black) and render invisible. The video's base ground is painted separately by the assembler from frame.md's canvas color onto the index #root; your full-bleed clip rides on top of it.
  • clip_missing_data_attrs — every class="clip" element has data-start / data-duration / data-track-index.
  • timeline_not_paused / timeline_not_registered — one paused timeline, registered at window.__timelines["<frame_id>"].
  • css_transition_used + repeat / yoyo / non-deterministic logic — none present (the renderer seeks frame-by-frame).
  • gsap_css_transform_conflict — never put a CSS transform (e.g. translateY(-50%) centering) on an element you then GSAP-animate a transform prop on (x / y / scale / rotation): GSAP overwrites the whole transform and silently drops the CSS centering (the element jumps). Center with margin / inset (or top/left + offset), fold the offset into the tween via xPercent / yPercent, or use fromTo (the rule exempts it).
  • Hero visibility — the main subject is visible by t <= 0.5s; entrance tweens use fromTo instead of CSS-hidden starting states.
  • exit_animation_on_non_final_scene — no exit tween unless you are the final frame.
  • No front-loading (not a slide) — the shot's pieces reveal on their voiceover cues across the duration, not all fired at t=0; a non-still frame keeps content arriving rather than holding a full canvas from ~25%.
  • Shot-sequence fidelity — every Scene in the time-coded sequence is realized as a phase, the blueprint's signature move (unless compose) is present and recognizable, and the shot reveals to the voiceover (never front-loaded at t=0).
  • Code-block cadence fits data-duration — for a code beat, the code-* block's internal cadence is set so the full block completes within the frame's data-duration (a long snippet at the default per-character speed overruns — the code never finishes and the chrome beats never play; see code-vocabulary.md).
  • font_family_without_font_face — every font you name has a matching local @font-face inside this template. Copy the auto-generated block from frame.md; the Claude preset's EB Garamond, Inter, and JetBrains Mono faces live in assets/fonts/. Never link Google Fonts and never name a family without a project file, including system CJK / Japanese / Devanagari families (Hiragino Sans, Yu Gothic, Noto Sans CJK, Noto Sans Devanagari, …): the render machine is clean headless Chrome, so absent faces silently fall back. For non-Latin visible text, use a shipped face that covers the script or romanize/transliterate it; otherwise it is out of scope.
  • Keep-out + no-narration-text (eyeball, no code) — nothing sits below the 83% cutoff; no narration sentence is rendered as visible text.