* 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.
17 KiB
Frame worker — product-launch per-frame composition author
You build one frame's composition HTML and nothing else. You run N-up, one frame each — siblings build the others. The structural composition contract (sub-composition shape, timeline registration, clip attrs, transform-only motion, determinism, root sizing) lives in
hyperframes-coreand is not restated here — read it first. This file carries only what's specific to a product-launch frame. Tempted to add a generic GSAP / timeline rule here? Wrong home — it belongs inhyperframes-core.
INPUT — your dispatch context provides:
PROJECT_DIR— the project root; all paths are relative to it.frame_id— e.g.03-feature. Use it verbatim as the composition id, thewindow.__timelineskey, and the file name (compositions/frames/03-feature.html) — that path is the frame'ssrcinSTORYBOARD.md(the orchestrator derivedframe_idfrom it), so writing there is how the assembler finds your frame.- Your
## Frame Nblock inSTORYBOARD.md(read it; 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 product-agnostic shot template this frame instantiates — the overall shape + its signature move. Read it for the shape;composemeans there's no template, sequence the shot from the Scene lines directly.focal:— which candidate is the hero.roles:— each candidate's role:cutoutforeground /backgroundfull-bleed / supporting — plus the real media available (eachpublic/<basename> — description; a[video]tag marks a.mp4motion source that cannot be mounted by this sub-composition worker).sfx:— the orchestrator's; you mount no audio.
frame.md(project root) — the design-truth: palette, 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. (A few rules link an optional runnable demo in the shared../hyperframes-animation/examples/<id>.html— open it only when a recipe is unclear.)../references/cut-catalog.md— the cut catalog (zoom-through / inverse / cut-the-curve / waterfall). When a Scene seam is a within-scene swap, a scene-to-scene cut, or a text-to-text line change, build it INSIDE your composition per this catalog (Z-scale + blur + opacity, or per-word x-staggers). 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 — compositions/frames/<frame_id>.html, one self-contained sub-composition. Writing it (past the self-check below) is your terminal action — you do not edit STORYBOARD.md, mint audio, assemble the index, run the CLI, or report back. The orchestrator picks up the file and marks the frame's 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 real assets where the sketch used stand-in 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.
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 entrance 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 the product's content. Brand text comes from your frame'sscene/ narrative. - Which motions / assets exist — named upstream in your block (the shot sequence's motion verbs +
blueprint:, the candidate media inroles:/focal:). Implement them; don't fetch or invent new ones (you have no asset-fetch tool — never fabricate an image URL). - 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 product-launch 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, cards, CTA, stats, brand mark) 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 hero type toward full-bleed. (Landscape's region is short, so vertical centering near 0.42 × height is fine.)
- Visible text is short motion-graphics copy — headline / stat / one-word emphasis (
"$83K","INSTANT"), never a sentence from the narration. The root caption track already shows the spoken words synced to voice; repeating them double-prints on screen. - 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 line, a card, a stat, an icon — 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 and reads as a glitch (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 (a held read beats bad motion). - 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, then instantiate it with this frame's content / assets / timing.compose→ no template; sequence the shot straight from the Scene lines. Whichever, never front-load the whole sequence att=0— pace the reveals to the voiceover. - Place each candidate by its
roles(thefocalis the hero): acutoutis a foreground subject — respect the 83% keep-out, lay text around it, not over its face; abackgroundis full-bleed and dimmed ~30–50% so foreground content stays legible. Frame files are sub-compositions. Audio remains orchestrator-owned: never author<audio>in a frame. An approved[video]candidate may be declared as a frame-local<video data-frame-video="approved" data-start="..." data-duration="..." data-track-index="...">;assemble-index.mjshoists it to the host root and translates its timing. Give every approved video explicit host geometry with numericdata-frame-video-x,data-frame-video-y,data-frame-video-width, anddata-frame-video-height; optionally setdata-frame-video-fit="cover|contain|fill|none|scale-down"(defaultcover). The assembler converts only those values to host CSS: frame-local classes and inline styles do not cross the hoist boundary. Do not use this declaration for audio, unapproved URLs, or videos without explicit timing and geometry. If no approved video is supplied, use an explicitly supplied static still/key art ([video-still]or another image candidate) as<img>; do not extract a frame, fabricate a URL, or silently embed an unapproved clip.
Workflow
- Read —
hyperframes-core's composition contract (the structural law), thenframe.md(the look) and your## Frame Nblock (the shot sequence +blueprint:/focal:/roles:/ assets). Then read the blueprint template../hyperframes-animation/blueprints/<id>.md(skip ifcompose) for the shot's shape and signature move, and open the rule recipeRULES_DIR/<id>.mdfor every named motion in the Scene lines (plus the shared../hyperframes-animation/examples/<id>.htmlwhen the recipe is unclear): you reproduce these mechanics, not improvise them. 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>, because the runtime only clones template contents and the assembled-projectlint/checkgate can miss an unwired blank sub-composition. - 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. Place the named assets, and find a visual idea that reinforces the beat, not a literal restyle of the words. - 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 finish — re-read your file against the checklist below and fix in place. Writing the file is your terminal action; 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— root is<template>-wrapped and carriesdata-composition-id="<frame_id>".- 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). font_family_without_font_face— every font you name has a matching@font-face(or@import) inside this file. Only use fonts that ship as files with the project: the families declared inframe.md(their.woff2live inassets/fonts/orcapture/assets/fonts/— point the@font-facesrcat the real file you find there). Never name a font that has no file, including system CJK / Japanese / Devanagari families (Hiragino Sans,Yu Gothic,Noto Sans CJK,Noto Sans Devanagari, …): the render machine is a clean headless Chrome with none of them installed, so the text silently falls back to a generic font and the typography is wrong in the MP4. For non-Latin or multilingual visible text, either use a shipped font that covers the script, or romanize / transliterate it (e.g.日本語→Japanese); if neither is possible it is out of scope for this frame — do not invent a font name.- Keep-out + no-narration-text (eyeball, no code) — nothing sits below the 83% cutoff; no narration sentence is rendered as visible text.