* feat(studio,cli): per-frame board comments, self-refreshing storyboard, status-aware preview landing Per-frame comment boxes on the storyboard board batch into .hyperframes/frame-comments.json (a resubmit wins per frame; unconsumed comments on other frames are kept). Submitted-but-unconsumed comments stay visible — a toolbar banner plus a per-tile echo — until the agent consumes the file; the banner also says what to do next (reply anything in the agent chat). The board keeps itself current: GET /projects/:id/signature exposes the watcher-cached project signature, the storyboard payload carries the signature it was derived from, and the view polls at 2s (hidden tabs skipped, re-checked on visibility), refetching in place with no loading flash. Posters bake the signature into their URL so tiles fill in as sketches land and a poster that failed mid-write retries on the next version; the empty state upgrades itself when STORYBOARD.md appears, and its handoff prompt now points the agent at the review loop and uses the parser's real status vocabulary (outline, not planned). preview lands the browser on the storyboard view while the board is the review surface — any frame built, or pure planning (srcs declared, none on disk yet) — and on the timeline once the video is assembled. * feat(skills): the review loop — plan, sketch, build as one shared process hyperframes-core/references/review-loop.md is the single source for the three-pass collaborative review: the plan proposed on a live board (§ 1), wireframe sketches marked built with one layout question (§ 2 — real words on plain blocks, run no CLI; a confirmed board is itself a valid deliverable when the user asked for a storyboard, not a video), the build dressing confirmed layouts (§ 3, worker or inline), and the final look (§ 4). Autonomous runs skip every gate and keep one question before render. The three narrative workflows' Steps 3/4/6 collapse to references plus their sketch stand-ins (captured-asset blocks for product-launch-video, plain code panels for pr-to-video); the confirmed-sketch handoff stays in each frame-worker prompt. general-video plans on a board for multi-scene narrative pieces in collaborative mode — its sketch pass is layout-before-animation with the user watching. The router treats "I want a storyboard" as a process request rather than a route, and closes exploratory intake by recommending a route plus how the run will review. The supporting contracts land next door: the comments channel (silent submit, one reply picks it up, check the file before the words) in brief-contract § 1; the sidecar schema and the built status rung in storyboard-format; the mode question asked first and alone in the three workflows' Step 0. * feat(media-use): user memory — remembered preferences and frozen recipes Two tiers of memory on media-use's existing two-tier storage split. Preferences (lightweight): confirmed brief answers — destination, aspect, language, mode, voice, style preset — recorded to the project's .media/preferences.json (committed, the team inherits it) and promoted to the personal ~/.media/preferences.json once the same value is confirmed in two different projects (a sightings ledger accumulates the cross-project evidence user-side, since project files can't see each other). prefs.mjs get/record; merge reads project-over-user; a changed value restarts its provenance. Recipes (heavyweight): one approved run frozen as a named, versioned bundle — frame.md, the storyboard skeleton (structure kept: durations, transitions, srcs, Video direction; statuses reset to outline; content blanked to per-frame fill-ins naming the beat's role), and the confirmed brief values. Named folders, not content hashes: re-freezing bumps version and archives <name>@v<N>; a freeze is already confirmed, so it promotes to the user tier immediately. recipe.mjs freeze/list/use, plus resolve --type recipe --entity <name> delegating like grade/lut. 16 new node --test cases; the media-use lib suite is 168/168. * feat(skills): wire user memory into the brief and the review loop brief-contract § 2 gains Remembered defaults: read the merged preferences before Round 2 and let a remembered value become the recommended option with a receipt naming its source project. Memory changes the default, never the question — every ask-marked field still gets asked, and what the request says this time beats what was picked last time. Record only what the user actually confirmed (a defaulted voice nobody chose is not an answer; a "go" that accepts the recommended defaults is). The first record announces itself once; after that the receipts carry the reminder. In autonomous mode a remembered value becomes the decided value, receipt included. The three narrative workflows read the remembered defaults before Round 2, record the confirmed answers at the Step 0 gate, record the chosen preset at the Step 2 gate (pr-to-video excepted — its preset is fixed), and fall back to the remembered voice when the request names none. general-video's discovery reads the same defaults. Recipes wire in at both ends: Step 0 checks for a matching recipe before the mode question — one question, plural-aware, and adopting one fills the brief, skips the design step, and drafts the storyboard from the frozen skeleton while every review gate still runs. The review loop's final look (§ 4) offers the freeze once after approval, and the confirmation teaches the recall phrase — the name is something the system reminds the user of, never something they must remember. The router recognizes a named recipe or "like last time" as a route. * docs(skills): the sketch pass names check, not the deprecated validate * feat(skills): intent-layer references — process, route briefs, capability menu, BRIEF.md format * feat(media-use): brief skeleton as the recipe's fourth artifact; flow/storyboard preference keys * feat(skills): the intent layer conducts every brief — workflows execute BRIEF.md * feat(skills): retire the mode preference key; sync catalog surfaces for intent layer * refactor(skills): dedupe router vs intent-layer guidance — one owner per rule * feat(skills): the design ask — own spec, pick by eye from showcases, or defer * docs(skills): the design ask says the honest line on capture routes * feat(skills): product-launch-video absorbs website-to-video as the tour angle * refactor(skills): keep product-launch-video pristine — a tour is brief intent, not a pipeline branch * feat(skills): production loop + genre lenses; general-video goes freeform (route yours, laws hold) * refactor(skills): /hyperframes is the front door - route tables and scope lists leave the workflows * docs(skills): review-loop pass across skill catalog * fix(cli): pass project dir to openStudioBrowser in background-server path * feat(skills): add pitch-round reference - verbalized sampling concept gate * feat(skills): wire pitch round into intent layer - completeness triage + route eligibility * feat(skills): editorial capability recommendations, handoff disciplines, menu-probe split * feat(skills): pitches carry their machinery; source-only-formed requests pitch the telling * feat(skills): companion goes director - ceiling treatment plus blueprint/rule citation discipline * fix(scripts): sandbox npx-leak guard - private npm global prefix keeps npx on the branch CLI * chore(skills): resync manifest hash after formatter pass reflowed general-video tables * fix(skills): recipe freeze reads workflow from BRIEF.md; style_preset records require workflow scope Two holes found by a live companion-run freeze: the agent-supplied --workflow contradicted the run's actual workflow (recipe.json said faceless-explainer, brief-skeleton said general-video), and the style_preset lookup missed because the preference had been recorded under the bare key. - freezeRecipe resolves the workflow from BRIEF.md frontmatter; the flag is a fallback for briefless projects and a contradicting flag is ignored (noted). - recordPreference refuses a bare style_preset — the scoped key is the only writable shape; freeze tolerates legacy bare records via read fallback. - review-loop § 4 / media-use SKILL / brief-format wording follow the machinery.
21 KiB
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, thewindow.__timelineskey, and the file name (compositions/frames/04-the-fix.html) — that path is the frame'ssrcinSTORYBOARD.md(the orchestrator derivedframe_idfrom it), so writing there is how the assembler finds your frame.- Your exact
## Frame Nblock (already extracted fromSTORYBOARD.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.0–Xs): … → 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 itsvoiceovercue (this is what keeps the shot from freezing). blueprint:— an id (or the literalcompose). 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;composemeans there's no template (common for a code beat — thecode-*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 namedcode-*block (+ the hunk); for the credits close, the avatar row.roles:— each element's role:foreground subject/backgroundfull-bleed /supporting. Most are invented elements you design; the only real assets are the creditsassets/<login>.pngavatars.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>.mdis 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 namedcode-*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'stransition_in+ the injector own that.- Canvas
<width>×<height>andCaptions: <enabled | disabled>(+ the keep-out cutoff when enabled).
Retry — if your context carries lint / check 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.
When a confirmed sketch exists
In collaborative runs the orchestrator wireframes the board first, so your target file may already exist as the frame's user-confirmed wireframe — your dispatch says whether it does (a file found on a retry is your own prior output, not a sketch). Read it first and keep its composition: the placement, hierarchy, and copy were approved — don't move or drop them. Everything else is yours to finish: the full frame.md treatment (the sketch is deliberately unstyled), the code-* block where the sketch held plain code, the finished invented visuals where it used plain blocks, and the motion — map each Scene onto a timeline phase, reveal each piece on its voiceover cue with fromTo entrances, adding DOM only where a phase needs it. The frame's landed state must still read as the approved wireframe, fully dressed.
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 namedcode-*block, don't hand-build code motion. Your## Frame Nscene/focalnames which block (e.g.code-diff,code-morph,code-typing); the orchestrator has already installed it (Step 5 pre-install). Readcode-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.__TOKENSwith that real code (the baked Shiki tokens) and setwindow.__BLOCK(effect,line,duration) so the full block completes within the frame'sdata-duration— a long snippet at the block's default per-character cadence overruns a short frame (the code never finishes typing).code-diff/code-morphneed 2 states (before, after); the others take one. Line indexing differs —code-highlightis 0-based,code-scroll1-based — don't off-by-one. - Integrate the filled block as this frame's composition per
hyperframes-core's sub-composition contract: itsdata-composition-idand itswindow.__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/−Mchrome from thescene. - 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.
- Use only the packet's
- 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 itsscenefor the behavior to animate. Unlike a code beat, the motion is yours to author (no block owns it):- If the
scenenames aflowchart/flowchart-vertical/data-chartblock, the orchestrator pre-installed it — fill + mount it like a code block (itsdata-composition-idandwindow.__timelines[...]key both become your<frame_id>). Otherwise hand-build the diagram in SVG / HTML / GSAP fromframe.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).
- If the
- The
creditsclose — the one frame with real assets. Itsasset_candidatesnames 2–6assets/<login>.pngavatars (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/ thevoiceoverline. 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 offrame.mdas your copy — it is a style spec, not content. Visible text comes from your frame'sscene/ narrative (and the real code, for a code beat). - Which motions exist — named upstream in your block (the shot sequence's motion verbs +
blueprint:+ thecode-*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.≤ 900on a 1080-tall frame,≤ 1600on a 1920-tall portrait). Holds even whenCaptions: 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.2–0.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 acode-*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 thevoiceoverreaches 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
voiceoverreaches it. For each named motion in a Scene, open its rule recipe underRULES_DIR/<id>.mdand reproduce its mechanics — never name-guess (a guess loses the signature move). Theblueprint: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 att=0. - Realize each element by its
roles(thefocalis the hero): aforeground subjectis the thing the eye lands on — respect the 83% keep-out and lay text around it; abackgroundis a full-bleed field / gradient / grid dimmed ~30–50% so foreground content stays legible;supportingelements are labels, secondary shapes, ambient layers. Invented elements are HTML/CSS/SVG you build; acode-*block /flowchart/data-chartis filled + mounted per the section above; the credits avatars render as<img>.
Workflow
- Read once, then build sequentially — read the compact structural contract and
frame.mdonce. 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>. - 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 itsvoiceovercue, 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 thecode-*block and choreograph the surround. - Author — write the full sub-composition to
compositions/frames/<frame_id>.html(rewrite to iterate; last write wins).<template>-wrapped root carryingdata-composition-id="<frame_id>"and styled via#root(not a class on that element — see the self-check below), exactly onegsap.timeline({ paused: true })registered atwindow.__timelines["<frame_id>"], built synchronously — per the core contract. - 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 / check 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 carriesdata-composition-id="<frame_id>"and a positivedata-durationmatching the packet.- Template transport — every
<style>and<script>block, including the GSAP load, lives inside<template>. subcomposition_root_styled_by_class— style the frame root via#root, never a class on thedata-composition-idelement: 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-durationclass="clip"background element on the lowest content track, not as abackgroundon the#root/data-composition-idelement. 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 hostbody(black) and render invisible. The video's base ground is painted separately by the assembler fromframe.md'scanvascolor onto the index#root; your full-bleed clip rides on top of it. clip_missing_data_attrs— everyclass="clip"element hasdata-start/data-duration/data-track-index.timeline_not_paused/timeline_not_registered— one paused timeline, registered atwindow.__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 CSStransform(e.g.translateY(-50%)centering) on an element you then GSAP-animate a transform prop on (x/y/scale/rotation): GSAP overwrites the wholetransformand silently drops the CSS centering (the element jumps). Center withmargin/inset(ortop/left+ offset), fold the offset into the tween viaxPercent/yPercent, or usefromTo(the rule exempts it).- Hero visibility — the main subject is visible by
t <= 0.5s; entrance tweens usefromToinstead 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
voiceovercues across the duration, not all fired att=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 att=0). - Code-block cadence fits
data-duration— for a code beat, thecode-*block's internal cadence is set so the full block completes within the frame'sdata-duration(a long snippet at the default per-character speed overruns — the code never finishes and the chrome beats never play; seecode-vocabulary.md). font_family_without_font_face— every font you name has a matching local@font-faceinside this template. Copy the auto-generated block fromframe.md; the Claude preset's EB Garamond, Inter, and JetBrains Mono faces live inassets/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.