From b9be0b26254fe7b759c6310036866f02d9d3d72b Mon Sep 17 00:00:00 2001 From: WaterrrForever Date: Wed, 15 Jul 2026 21:19:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(skills,studio,media-use):=20the=20intent?= =?UTF-8?q?=20layer,=20review=20loop,=20and=20user=20memory=20=E2=80=94=20?= =?UTF-8?q?BRIEF.md,=20companion=20mode,=20recipes;=20/website-to-video=20?= =?UTF-8?q?folds=20into=20/product-launch-video=20(#2133)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 @v; a freeze is already confirmed, so it promotes to the user tier immediately. recipe.mjs freeze/list/use, plus resolve --type recipe --entity 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. --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- .cursor-plugin/plugin.json | 2 +- AGENTS.md | 7 +- CLAUDE.md | 15 +- README.md | 37 +- docs/guides/authentication.mdx | 2 +- docs/guides/pipeline.mdx | 6 +- docs/guides/prompting.mdx | 3 +- docs/guides/skills.mdx | 9 +- docs/guides/website-to-video.mdx | 2 +- docs/launch-videos.mdx | 2 +- docs/packages/cli.mdx | 2 +- docs/quickstart.mdx | 2 +- .../cli/src/capture/agentPromptGenerator.ts | 4 +- packages/cli/src/commands/preview.test.ts | 53 ++ packages/cli/src/commands/preview.ts | 66 +- packages/cli/src/templates/_shared/AGENTS.md | 7 +- packages/cli/src/templates/_shared/CLAUDE.md | 7 +- .../src/helpers/projectSignature.ts | 20 + packages/studio-server/src/routes/preview.ts | 22 +- .../studio-server/src/routes/projects.test.ts | 44 + packages/studio-server/src/routes/projects.ts | 10 + .../src/routes/storyboard.test.ts | 13 + .../studio-server/src/routes/storyboard.ts | 10 +- .../src/components/storyboard/FramePoster.tsx | 28 +- .../storyboard/StoryboardFrameFocus.tsx | 4 + .../storyboard/StoryboardFrameTile.tsx | 31 +- .../components/storyboard/StoryboardGrid.tsx | 24 +- .../storyboard/StoryboardLoaded.tsx | 60 +- .../components/storyboard/StoryboardView.tsx | 11 +- .../storyboard/frameComments.test.ts | 95 +++ .../components/storyboard/frameComments.ts | 115 +++ .../components/storyboard/useFrameComments.ts | 82 ++ .../hooks/useProjectSignaturePoll.test.tsx | 135 ++++ .../src/hooks/useProjectSignaturePoll.ts | 68 ++ packages/studio/src/hooks/useStoryboard.ts | 27 +- scripts/test-skills-fresh.sh | 31 +- skills-manifest.json | 48 +- skills/dashboard.html | 0 skills/embedded-captions/SKILL.md | 81 +- skills/faceless-explainer/SKILL.md | 64 +- .../sub-agents/frame-worker.md | 10 +- skills/general-video/SKILL.md | 269 ++++--- skills/hyperframes-animation/SKILL.md | 4 +- .../adapters/gsap-transforms-and-perf.md | 2 +- skills/hyperframes-animation/adapters/gsap.md | 4 +- .../blueprints/dataviz-countup.md | 10 +- .../blueprints/device-surface-showcase.md | 11 +- skills/hyperframes-animation/rules-index.md | 4 +- .../rules/counting-dynamic-scale.md | 46 +- .../rules/motion-blur-streak.md | 2 +- .../rules/stat-bars-and-fills.md | 2 +- skills/hyperframes-cli/SKILL.md | 235 +++--- skills/hyperframes-cli/references/beats.md | 19 + skills/hyperframes-cli/references/cloud.md | 7 +- skills/hyperframes-cli/references/cloudrun.md | 62 ++ .../references/compare-and-batch.md | 105 +++ skills/hyperframes-cli/references/lambda.md | 101 ++- .../references/lint-validate-inspect.md | 4 +- .../references/preview-render.md | 4 + skills/hyperframes-core/SKILL.md | 39 +- .../references/brief-contract.md | 128 ++- .../references/brief-format.md | 72 ++ .../references/data-attributes.md | 6 +- .../references/determinism-rules.md | 4 +- .../references/production-loop.md | 21 + .../references/review-loop.md | 41 + .../references/storyboard-format.md | 31 + .../references/sub-compositions.md | 6 +- .../references/variables-and-media.md | 2 +- skills/hyperframes-creative/SKILL.md | 2 +- .../references/motion-principles.md | 10 +- .../references/prompt-expansion.md | 2 +- .../references/story-spine.md | 2 +- .../references/video-composition.md | 10 +- skills/hyperframes-keyframes/SKILL.md | 30 +- skills/hyperframes-registry/SKILL.md | 24 +- .../references/discovery.md | 22 +- .../references/templates.md | 2 +- skills/hyperframes/SKILL.md | 261 +++--- .../hyperframes/references/capability-menu.md | 47 ++ skills/hyperframes/references/pitch-round.md | 40 + skills/hyperframes/references/route-briefs.md | 85 ++ .../hyperframes/references/skill-lifecycle.md | 33 + .../references/workflow-catalog.md | 70 ++ skills/media-use/SKILL.md | 28 + skills/media-use/scripts/lib/manifest.mjs | 1 + skills/media-use/scripts/lib/prefs-store.mjs | 180 +++++ .../scripts/lib/prefs-store.test.mjs | 189 +++++ skills/media-use/scripts/lib/recipe-store.mjs | 367 +++++++++ .../scripts/lib/recipe-store.test.mjs | 367 +++++++++ skills/media-use/scripts/prefs.mjs | 70 ++ skills/media-use/scripts/recipe.mjs | 103 +++ skills/media-use/scripts/resolve.mjs | 26 + skills/motion-graphics/SKILL.md | 68 +- skills/motion-graphics/agents/builder.md | 6 +- skills/motion-graphics/agents/finalize.md | 12 +- .../categories/kinetic-type/module.md | 2 +- .../categories/webpage/module.md | 2 +- .../references/builder-contract.md | 6 +- skills/music-to-video/SKILL.md | 15 +- .../music-to-video/sub-agents/frame-worker.md | 54 +- skills/pr-to-video/SKILL.md | 76 +- skills/pr-to-video/sub-agents/frame-worker.md | 8 +- skills/product-launch-video/SKILL.md | 62 +- .../sub-agents/frame-worker.md | 10 +- skills/remotion-to-hyperframes/SKILL.md | 2 +- skills/slideshow/SKILL.md | 4 +- skills/talking-head-recut/SKILL.md | 4 +- skills/website-to-video/SKILL.md | 150 ---- skills/website-to-video/assets/sfx/CREDITS.md | 35 - skills/website-to-video/assets/sfx/chime.mp3 | Bin 27309 -> 0 bytes .../assets/sfx/click-soft.mp3 | Bin 11702 -> 0 bytes skills/website-to-video/assets/sfx/click.mp3 | Bin 11702 -> 0 bytes skills/website-to-video/assets/sfx/error.mp3 | Bin 51827 -> 0 bytes .../website-to-video/assets/sfx/glitch-1.mp3 | Bin 84427 -> 0 bytes .../website-to-video/assets/sfx/glitch-2.mp3 | Bin 112128 -> 0 bytes .../website-to-video/assets/sfx/glitch-3.mp3 | Bin 99072 -> 0 bytes .../assets/sfx/impact-bass-1.mp3 | Bin 67709 -> 0 bytes .../assets/sfx/impact-bass-2.mp3 | Bin 82944 -> 0 bytes .../website-to-video/assets/sfx/key-press.mp3 | Bin 3909 -> 0 bytes .../website-to-video/assets/sfx/manifest.json | 97 --- .../assets/sfx/notification.mp3 | Bin 78576 -> 0 bytes skills/website-to-video/assets/sfx/ping.mp3 | Bin 26400 -> 0 bytes skills/website-to-video/assets/sfx/pop.mp3 | Bin 23040 -> 0 bytes skills/website-to-video/assets/sfx/riser.mp3 | Bin 321024 -> 0 bytes .../website-to-video/assets/sfx/sparkle.mp3 | Bin 57678 -> 0 bytes skills/website-to-video/assets/sfx/typing.mp3 | Bin 26852 -> 0 bytes .../assets/sfx/whoosh-cinematic.mp3 | Bin 177408 -> 0 bytes .../assets/sfx/whoosh-short.mp3 | Bin 18390 -> 0 bytes skills/website-to-video/assets/sfx/whoosh.mp3 | Bin 18390 -> 0 bytes .../references/beat-builder-guide.md | 263 ------ .../references/capabilities.md | 713 ---------------- .../references/step-0-capture.md | 55 -- .../references/step-1-design.md | 333 -------- .../references/step-2-brief.md | 178 ---- .../references/step-3-storyboard.md | 574 ------------- .../website-to-video/references/step-4-vo.md | 230 ------ .../references/step-5-build.md | 483 ----------- .../references/step-6-validate.md | 341 -------- .../website-to-video/scripts/w2h-verify.mjs | 759 ------------------ 143 files changed, 3831 insertions(+), 5188 deletions(-) create mode 100644 packages/cli/src/commands/preview.test.ts create mode 100644 packages/studio/src/components/storyboard/frameComments.test.ts create mode 100644 packages/studio/src/components/storyboard/frameComments.ts create mode 100644 packages/studio/src/components/storyboard/useFrameComments.ts create mode 100644 packages/studio/src/hooks/useProjectSignaturePoll.test.tsx create mode 100644 packages/studio/src/hooks/useProjectSignaturePoll.ts delete mode 100644 skills/dashboard.html create mode 100644 skills/hyperframes-cli/references/beats.md create mode 100644 skills/hyperframes-cli/references/cloudrun.md create mode 100644 skills/hyperframes-cli/references/compare-and-batch.md create mode 100644 skills/hyperframes-core/references/brief-format.md create mode 100644 skills/hyperframes-core/references/production-loop.md create mode 100644 skills/hyperframes-core/references/review-loop.md create mode 100644 skills/hyperframes/references/capability-menu.md create mode 100644 skills/hyperframes/references/pitch-round.md create mode 100644 skills/hyperframes/references/route-briefs.md create mode 100644 skills/hyperframes/references/skill-lifecycle.md create mode 100644 skills/hyperframes/references/workflow-catalog.md create mode 100644 skills/media-use/scripts/lib/prefs-store.mjs create mode 100644 skills/media-use/scripts/lib/prefs-store.test.mjs create mode 100644 skills/media-use/scripts/lib/recipe-store.mjs create mode 100644 skills/media-use/scripts/lib/recipe-store.test.mjs create mode 100644 skills/media-use/scripts/prefs.mjs create mode 100644 skills/media-use/scripts/recipe.mjs delete mode 100644 skills/website-to-video/SKILL.md delete mode 100644 skills/website-to-video/assets/sfx/CREDITS.md delete mode 100644 skills/website-to-video/assets/sfx/chime.mp3 delete mode 100644 skills/website-to-video/assets/sfx/click-soft.mp3 delete mode 100644 skills/website-to-video/assets/sfx/click.mp3 delete mode 100644 skills/website-to-video/assets/sfx/error.mp3 delete mode 100644 skills/website-to-video/assets/sfx/glitch-1.mp3 delete mode 100644 skills/website-to-video/assets/sfx/glitch-2.mp3 delete mode 100644 skills/website-to-video/assets/sfx/glitch-3.mp3 delete mode 100644 skills/website-to-video/assets/sfx/impact-bass-1.mp3 delete mode 100644 skills/website-to-video/assets/sfx/impact-bass-2.mp3 delete mode 100644 skills/website-to-video/assets/sfx/key-press.mp3 delete mode 100644 skills/website-to-video/assets/sfx/manifest.json delete mode 100644 skills/website-to-video/assets/sfx/notification.mp3 delete mode 100644 skills/website-to-video/assets/sfx/ping.mp3 delete mode 100644 skills/website-to-video/assets/sfx/pop.mp3 delete mode 100644 skills/website-to-video/assets/sfx/riser.mp3 delete mode 100644 skills/website-to-video/assets/sfx/sparkle.mp3 delete mode 100644 skills/website-to-video/assets/sfx/typing.mp3 delete mode 100644 skills/website-to-video/assets/sfx/whoosh-cinematic.mp3 delete mode 100644 skills/website-to-video/assets/sfx/whoosh-short.mp3 delete mode 100644 skills/website-to-video/assets/sfx/whoosh.mp3 delete mode 100644 skills/website-to-video/references/beat-builder-guide.md delete mode 100644 skills/website-to-video/references/capabilities.md delete mode 100644 skills/website-to-video/references/step-0-capture.md delete mode 100644 skills/website-to-video/references/step-1-design.md delete mode 100644 skills/website-to-video/references/step-2-brief.md delete mode 100644 skills/website-to-video/references/step-3-storyboard.md delete mode 100644 skills/website-to-video/references/step-4-vo.md delete mode 100644 skills/website-to-video/references/step-5-build.md delete mode 100644 skills/website-to-video/references/step-6-validate.md delete mode 100644 skills/website-to-video/scripts/w2h-verify.mjs diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 98c75187f..9e6444da7 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ { "name": "hyperframes", "source": "./", - "description": "HyperFrames by HeyGen. Write HTML, render video. Compositions, GSAP and runtime adapter animations, captions, voiceovers, audio-reactive visuals, and website-to-video capture for HyperFrames.", + "description": "HyperFrames by HeyGen. Write HTML, render video. Compositions, GSAP and runtime adapter animations, captions, voiceovers, audio-reactive visuals, and website capture for HyperFrames.", "homepage": "https://hyperframes.heygen.com", "license": "Apache-2.0" }, diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index c3eb47fa1..7ec84fe43 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "hyperframes", - "description": "HyperFrames by HeyGen. Write HTML, render video. Compositions, GSAP and runtime adapter animations, captions, voiceovers, audio-reactive visuals, and website-to-video capture for HyperFrames.", + "description": "HyperFrames by HeyGen. Write HTML, render video. Compositions, GSAP and runtime adapter animations, captions, voiceovers, audio-reactive visuals, and website capture for HyperFrames.", "version": "0.7.58", "author": { "name": "HeyGen", diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index b74f3fbd4..c3b2b89af 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "hyperframes", + "description": "Write HTML, render video. Compositions, Tailwind v4 styles, GSAP and runtime adapter animations, captions, voiceovers, audio-reactive visuals, and website capture for HyperFrames.", "version": "0.7.58", - "description": "Write HTML, render video. Compositions, Tailwind v4 styles, GSAP and runtime adapter animations, captions, voiceovers, audio-reactive visuals, and website-to-video capture for HyperFrames.", "author": { "name": "HeyGen", "email": "hyperframes@heygen.com", diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 68b78e07e..1f7c83450 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -2,8 +2,8 @@ "$schema": "https://cursor.com/schemas/cursor-plugin/plugin.json", "name": "hyperframes", "displayName": "HyperFrames by HeyGen", + "description": "Write HTML, render video. Compositions, Tailwind v4 styles, GSAP and runtime adapter animations, captions, voiceovers, audio-reactive visuals, and website capture for HyperFrames.", "version": "0.7.58", - "description": "Write HTML, render video. Compositions, Tailwind v4 styles, GSAP and runtime adapter animations, captions, voiceovers, audio-reactive visuals, and website-to-video capture for HyperFrames.", "author": { "name": "HeyGen", "email": "hyperframes@heygen.com" diff --git a/AGENTS.md b/AGENTS.md index 1c1cd9eff..64374bf02 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,10 +10,9 @@ This repo ships AI agent skills via [vercel-labs/skills](https://github.com/verc npx skills add heygen-com/hyperframes ``` -**Creation workflows** route through one entry skill — read `/hyperframes` first: it orients you to the whole surface and maps "make me a…" intent — usually a video, but also a navigable deck (`/slideshow`) or a composition port (`/remotion-to-hyperframes`) — to a concrete workflow. Consult it before invoking a specific workflow: +**Creation workflows** route through one entry skill — read `/hyperframes` first: it orients you to the whole surface, confirms the brief up front (the intent layer), and maps "make me a…" intent — usually a video, but also a navigable deck (`/slideshow`) or a composition port (`/remotion-to-hyperframes`) — to a concrete workflow. Consult it before invoking a specific workflow: -- `/product-launch-video` — a **product** URL (or a pre-written script / text brief in no-capture mode) → product launch / promo video, up to ~3 min (sweet spot ~30-90s). -- `/website-to-video` — a **general** website / URL → a video _of_ the site (tour / showcase / social clip from captured screenshots + assets); for a product **launch / promo**, use `/product-launch-video`. +- `/product-launch-video` — any **website** URL (or a pre-written script / text brief in no-capture mode) → a product launch / promo video, or a site tour / showcase featuring the site's own captured screens; up to ~3 min (sweet spot ~30-90s). - `/faceless-explainer` — arbitrary text, **no URL and no website capture** → faceless explainer, up to ~3 min (sweet spot ~30-90s); every visual is LLM-invented (typography / abstract graphics / diagram / data-viz). - `/embedded-captions` — an existing talking-head video (MP4) → the same footage with captions / subtitles added (verbatim rail + embedded climax, or pure-cinematic embed); the footage itself is untouched (no NLE-style editing). - `/talking-head-recut` — an existing talking-head / interview / podcast video (MP4) → the same footage packaged with designed **graphic overlays** (kinetic titles, lower-thirds, data callouts, pull-quotes, side panels, pip) synced to the transcript; the clip plays unchanged underneath, footage untouched. For plain captions/subtitles → `/embedded-captions`. @@ -21,7 +20,7 @@ npx skills add heygen-com/hyperframes - `/motion-graphics` — a short (typically under 10s) design-led **motion graphic**, motion-is-the-message, no narration: kinetic type, a stat / number count-up, a chart, a logo sting, a lower-third / overlay, or an animated tweet / headline / captured-page highlight; rendered to MP4 or a transparent overlay. Longer / narrated / custom → `/general-video`. - `/music-to-video` — a **music track** (audio file, or video to pull audio from) → beat-synced video (lyric / slideshow / kinetic promo). Music drives pacing; user-supplied images / videos are cut onto the same beat grid. - `/slideshow` — a **presentation / pitch deck / interactive deck** — discrete slides, fragment reveals, branching, hotspot navigation, presenter mode. Output is a navigable deck, not a rendered video. -- `/general-video` — fallback for any other video creation (title card, longer brand / sizzle reel, multi-scene montage, static loop, custom composition); the original hyperframes flow — design → plan → layout → build → validate, any length. +- `/general-video` — fallback for any other video creation (title card, longer brand / sizzle reel, multi-scene montage, static loop, custom composition) and the home of **companion mode** — co-create with the full HyperFrames toolbox; the original hyperframes flow — design → plan → layout → build → validate, any length. **Porting an existing composition?** `/remotion-to-hyperframes` translates a Remotion (React) video composition into HyperFrames HTML — a source migration, separate from the creation workflows above. diff --git a/CLAUDE.md b/CLAUDE.md index abd8a019f..a9a4d6268 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,22 +4,21 @@ Open-source video rendering framework: write HTML, render video. ## Skills -This repo ships 20 AI agent skills via [vercel-labs/skills](https://github.com/vercel-labs/skills). Install them before writing compositions — they encode framework-specific patterns that generic docs don't cover. +This repo ships 19 AI agent skills via [vercel-labs/skills](https://github.com/vercel-labs/skills). Install them before writing compositions — they encode framework-specific patterns that generic docs don't cover. ```bash npx skills add heygen-com/hyperframes --full-depth # interactive picker -npx skills add heygen-com/hyperframes --all --full-depth # install all 20 (skips picker) +npx skills add heygen-com/hyperframes --all --full-depth # install all 19 (skips picker) npx skills add heygen-com/hyperframes --skill --full-depth # just one (bare name, no leading slash) ``` Keep `--full-depth`: it installs the current `main`. Without it, `skills add` fetches the skills.sh registry blob, which lags `main` by hours (you'd get a stale skill). `hyperframes skills update` already uses full-depth. -**`/hyperframes` is the entry skill — read it first.** It's the capability map for the domain skills below AND the intent router for the creation workflows. The full README skills section mirrors this list; keep them in sync (see "Skill catalog maintenance" below). +**`/hyperframes` is the entry skill — read it first.** It's the capability map for the domain skills below, the intent layer that confirms every creation brief up front, AND the intent router for the creation workflows. The full README skills section mirrors this list; keep them in sync (see "Skill catalog maintenance" below). ### Creation workflows -- `/product-launch-video` — a **product** URL (or a pre-written script / text brief in no-capture mode) → product launch / promo video, up to ~3 min (sweet spot ~30-90s). -- `/website-to-video` — a **general** website / URL → a video _of_ the site (tour / showcase / social clip from captured screenshots + assets); for a product **launch / promo**, use `/product-launch-video`. +- `/product-launch-video` — any **website** URL (or a pre-written script / text brief in no-capture mode) → a product launch / promo video, or a site tour / showcase featuring the site's own captured screens; up to ~3 min (sweet spot ~30-90s). - `/faceless-explainer` — arbitrary text, **no URL and no website capture** → faceless explainer, up to ~3 min (sweet spot ~30-90s); every visual is LLM-invented (typography / abstract graphics / diagram / data-viz). - `/pr-to-video` — a GitHub PR (URL / `owner/repo#N` / "this PR") → code-change explainer, up to ~3 min (changelog / feature reveal / fix / refactor). A PR link, not a product website. - `/embedded-captions` — an existing talking-head video (MP4) → the same footage with captions / subtitles added (verbatim rail + embedded climax, or pure-cinematic embed); the footage itself is untouched (no NLE-style editing). @@ -27,7 +26,7 @@ Keep `--full-depth`: it installs the current `main`. Without it, `skills add` fe - `/motion-graphics` — a short (typically under 10s) design-led **motion graphic**, motion-is-the-message, no narration: kinetic type, a stat / number count-up, a chart, a logo sting, a lower-third / overlay, or an animated tweet / headline / captured-page highlight; rendered to MP4 or a transparent overlay. Longer / narrated / custom → `/general-video`. - `/music-to-video` — a **music track** (audio file, or video to pull audio from) → beat-synced video (lyric / slideshow / kinetic promo). Music drives pacing; user-supplied images / videos are cut onto the same beat grid. - `/slideshow` — a **presentation / pitch deck / interactive deck** — discrete slides, fragment reveals, branching, hotspot navigation, presenter mode. Output is a navigable deck, not a rendered video. -- `/general-video` — fallback for any other video creation (title card, longer brand / sizzle reel, multi-scene montage, static loop, custom composition); the original hyperframes flow — design → plan → layout → build → validate, any length. +- `/general-video` — fallback for any other video creation (title card, longer brand / sizzle reel, multi-scene montage, static loop, custom composition) and the home of **companion mode** — co-create with the full HyperFrames toolbox; the original hyperframes flow — design → plan → layout → build → validate, any length. - `/remotion-to-hyperframes` — port an existing Remotion (React) composition to HyperFrames HTML. One-way migration, not creation. ### Domain skills (loaded on demand) @@ -49,9 +48,9 @@ When adding a new skill, or substantially renaming / repurposing an existing one 1. The skill list above (CLAUDE.md) AND the workflow list in the root `AGENTS.md` (it carries workflows only, no domain-skill section) AND the `## Skills` section in `README.md` AND `docs/guides/skills.mdx` (rendered at [hyperframes.heygen.com/guides/skills](https://hyperframes.heygen.com/guides/skills)). Out-of-date entries silently kill discovery. 2. The scaffolded project template `packages/cli/src/templates/_shared/CLAUDE.md` + `AGENTS.md` — written into every `hyperframes init` project, so a stale entry there ships to users. The two template files must stay byte-identical. -3. If the skill changes the routing surface for "make a video" requests, also update the capability map and intent router in `skills/hyperframes/SKILL.md` — that's the canonical router agents read first. +3. If the skill changes the routing surface for "make a video" requests, also update the routing table + intent layer in `skills/hyperframes/SKILL.md` AND that workflow's entry in `skills/hyperframes/references/workflow-catalog.md` (the input/output/trigger contract the router reads before the workflow is installed) — plus its row in `references/route-briefs.md` when the route asks questions. 4. Mirror the Router / Creation workflows / Domain skills grouping across all surfaces so a skill always lives in the same column. -5. Skill count appears in the README and CLAUDE.md intro lines ("20 AI agent skills…") — update on add/remove. The `docs/guides/skills.mdx` page and the CLI templates deliberately omit a count to avoid drift; keep them count-free. +5. Skill count appears in the README and CLAUDE.md intro lines ("19 AI agent skills…") — update on add/remove. The `docs/guides/skills.mdx` page and the CLI templates deliberately omit a count to avoid drift; keep them count-free. The skill's own `SKILL.md` frontmatter `description:` is the source of truth for the one-line "use when" blurb; copy from there into the catalog rather than paraphrasing. diff --git a/README.md b/README.md index 1ffff1895..7d4b3c991 100644 --- a/README.md +++ b/README.md @@ -51,33 +51,32 @@ The skills teach agents the HyperFrames production loop: plan the video, write v ## Skills -HyperFrames ships 20 skills agents load on demand. Read `/hyperframes` first — it's the router and capability map; it picks a workflow for any "make me a…" request — video, deck, or composition port — and points to the domain skills below. +HyperFrames ships 19 skills agents load on demand. Read `/hyperframes` first — it's the router and capability map; it picks a workflow for any "make me a…" request — video, deck, or composition port — and points to the domain skills below. -Run `npx skills add heygen-com/hyperframes --full-depth` for the interactive picker — it lists the **core set** under the "Core Skills" group and the on-demand creation workflows under "Other". Use `npx skills add heygen-com/hyperframes --all --full-depth` to install all 20 at once (skips the picker), or `npx skills add heygen-com/hyperframes --skill --full-depth` for just one (bare name, no leading `/`). Keep `--full-depth` — it installs the current `main`; without it `skills add` fetches the skills.sh blob, which lags by hours. +Run `npx skills add heygen-com/hyperframes --full-depth` for the interactive picker — it lists the **core set** under the "Core Skills" group and the on-demand creation workflows under "Other". Use `npx skills add heygen-com/hyperframes --all --full-depth` to install all 19 at once (skips the picker), or `npx skills add heygen-com/hyperframes --skill --full-depth` for just one (bare name, no leading `/`). Keep `--full-depth` — it installs the current `main`; without it `skills add` fetches the skills.sh blob, which lags by hours. Installs stay lean after that: `npx hyperframes init` keeps the **core set** fresh (the router, the `hyperframes-*` domain skills, and `media-use` — plus whatever is already installed; `/figma` stays on demand) and never expands a partial install; the creation workflows install **on demand** — the router runs `npx hyperframes skills update ` before entering one. Nothing re-pulls the full set behind your back. ### Router -| Skill | Use when | -| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `/hyperframes` | **Read first** for any request to make / create / edit / animate / render a video, animation, or motion graphic. Capability map for the domain skills and intent router for the creation workflows below. | +| Skill | Use when | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `/hyperframes` | **Read first** for any request to make / create / edit / animate / render a video, animation, or motion graphic. Capability map for the domain skills, the intent layer that confirms every creation brief up front, and intent router for the creation workflows below. | ### Creation workflows -| Skill | Use when | -| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `/product-launch-video` | Marketing / launching / promoting a **product** — from its URL, a brief, or a script (even if the site is only named). Up to ~3 min (sweet spot 30-90s). | -| `/website-to-video` | Turning a **general website** into a video — site tour, portfolio / landing-page showcase, social clip from the site's own visuals. | -| `/faceless-explainer` | **Explaining a topic / concept** from arbitrary text — no product, no URL, no website capture; every visual is LLM-invented (typography / abstract / diagram / data-viz). | -| `/pr-to-video` | A **GitHub pull request** (PR URL, `owner/repo#N` ref, or "this PR") → changelog / feature-reveal / fix / refactor explainer, read via the `gh` CLI. | -| `/embedded-captions` | Adding **captions / subtitles** to an existing talking-head video (footage untouched) — verbatim rail, embedded climax behind the subject, or pure-cinematic embed. | -| `/talking-head-recut` | Packaging an existing talking-head / interview / podcast video with **designed graphic overlays** — lower-thirds, data callouts, kinetic titles, pull-quotes, side panels, PiP. | -| `/motion-graphics` | A short, **unnarrated, design-led motion graphic** (~under 10s) — kinetic type, stat / chart hit, logo sting, lower-third, animated tweet / headline. MP4 or transparent overlay. | -| `/music-to-video` | A **music track** (audio file, or video to pull audio from) → a **beat-synced** video — lyric, slideshow, or kinetic promo; music drives pacing. | -| `/slideshow` | A **presentation / pitch deck / interactive deck** — discrete slides, fragment reveals, branching, hotspot navigation, presenter mode. Output is a navigable deck, not a rendered video. | -| `/general-video` | **Anything else** — longer or multi-scene pieces, brand / sizzle reel, title card, static loop, freeform composition. Input- and length-agnostic fallback. | -| `/remotion-to-hyperframes` | **Porting an existing Remotion** (React) composition's source to HyperFrames HTML. One-way migration, not creation. | +| Skill | Use when | +| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `/product-launch-video` | Any **website** — marketing / launching / promoting a product (from its URL, a brief, or a script), or a site tour / showcase / social clip featuring the site's own visuals. Up to ~3 min (sweet spot 30-90s). | +| `/faceless-explainer` | **Explaining a topic / concept** from arbitrary text — no product, no URL, no website capture; every visual is LLM-invented (typography / abstract / diagram / data-viz). | +| `/pr-to-video` | A **GitHub pull request** (PR URL, `owner/repo#N` ref, or "this PR") → changelog / feature-reveal / fix / refactor explainer, read via the `gh` CLI. | +| `/embedded-captions` | Adding **captions / subtitles** to an existing talking-head video (footage untouched) — verbatim rail, embedded climax behind the subject, or pure-cinematic embed. | +| `/talking-head-recut` | Packaging an existing talking-head / interview / podcast video with **designed graphic overlays** — lower-thirds, data callouts, kinetic titles, pull-quotes, side panels, PiP. | +| `/motion-graphics` | A short, **unnarrated, design-led motion graphic** (~under 10s) — kinetic type, stat / chart hit, logo sting, lower-third, animated tweet / headline. MP4 or transparent overlay. | +| `/music-to-video` | A **music track** (audio file, or video to pull audio from) → a **beat-synced** video — lyric, slideshow, or kinetic promo; music drives pacing. | +| `/slideshow` | A **presentation / pitch deck / interactive deck** — discrete slides, fragment reveals, branching, hotspot navigation, presenter mode. Output is a navigable deck, not a rendered video. | +| `/general-video` | **Anything else** — longer or multi-scene pieces, brand / sizzle reel, title card, static loop, freeform composition. Input- and length-agnostic fallback, and the home of companion mode (co-create with the full toolbox). | +| `/remotion-to-hyperframes` | **Porting an existing Remotion** (React) composition's source to HyperFrames HTML. One-way migration, not creation. | ### Domain skills (loaded on demand) @@ -115,7 +114,7 @@ Need ideas? Browse the [Showcase](https://hyperframes.heygen.com/showcase) for f - PR walkthroughs with animated code diffs, narration, and captions - Data visualizations, chart races, and map animations - Social videos with kinetic captions, overlays, and music -- Docs-to-video, PDF-to-video, and website-to-video explainers +- Docs-to-video, PDF-to-video, and site-tour explainers - Reusable motion graphics for automated content pipelines ## Frame.md diff --git a/docs/guides/authentication.mdx b/docs/guides/authentication.mdx index a16a42ca8..797b79e67 100644 --- a/docs/guides/authentication.mdx +++ b/docs/guides/authentication.mdx @@ -59,7 +59,7 @@ Each capability picks the **first available provider** in order; the last is alw | **Voice (TTS)** | HeyGen → ElevenLabs → Kokoro | `HEYGEN_API_KEY` → `HYPERFRAMES_API_KEY` → `~/.heygen` · then `ELEVENLABS_API_KEY` | Kokoro: `pip install kokoro-onnx soundfile` | | **Music (BGM)** | HeyGen library → Lyria → MusicGen | HeyGen credential (above) · then `GEMINI_API_KEY` → `GOOGLE_API_KEY` | MusicGen: `pip install transformers torch soundfile numpy` | | **Sound effects** | HeyGen library → bundled library | HeyGen credential (above) | bundled — no deps | -| **Capture descriptions** | OpenRouter → Gemini | `OPENROUTER_API_KEY` → `GEMINI_API_KEY` | — (optional; for [website-to-video](/guides/website-to-video)) | +| **Capture descriptions** | OpenRouter → Gemini | `OPENROUTER_API_KEY` → `GEMINI_API_KEY` | — (optional; for [website capture](/guides/website-to-video)) | Run `npx hyperframes doctor` to check which local dependencies are installed. The media skills also run `hyperframes auth status` as a preflight before generating, so you always know whether a run will use HeyGen or a local engine before it starts. diff --git a/docs/guides/pipeline.mdx b/docs/guides/pipeline.mdx index 8298adf98..71b273ccd 100644 --- a/docs/guides/pipeline.mdx +++ b/docs/guides/pipeline.mdx @@ -5,7 +5,7 @@ description: "The 7-step pipeline for producing any Hyperframes video: capture, Every well-structured Hyperframes video flows through the same 7 steps, whether it starts from a website, a PDF, a CSV, or a blank page. Each step produces a named artifact that the next step depends on, so your AI agent (and you) always know what's done, what's next, and where the creative decisions live on disk. -This pipeline is the backbone of the [website-to-video workflow](/guides/website-to-video), but it's just as useful when you're scripting a brand reel from scratch, turning research notes into a launch teaser, or learning Hyperframes for the first time. Most of the production-grade [launch videos](/launch-videos) HeyGen ships are organized this way. +This pipeline is the backbone of the [website-to-video guide](/guides/website-to-video), but it's just as useful when you're scripting a brand reel from scratch, turning research notes into a launch teaser, or learning Hyperframes for the first time. Most of the production-grade [launch videos](/launch-videos) HeyGen ships are organized this way. ## The seven steps @@ -196,7 +196,7 @@ Each artifact is a checkpoint, so you can stop, hand off to a human reviewer, or The pipeline is the recommended structure for: -- Capturing a website with the [/website-to-video](/guides/website-to-video) skill, which follows it end-to-end. +- Capturing a website with the [/product-launch-video](/guides/website-to-video) workflow, which follows it end-to-end. - Shipping a product launch. Most of the [HeyGen launch videos](/launch-videos) use this artifact layout. - Any narrative video with three or more beats, where a storyboard pays for itself. - Learning Hyperframes, because the artifacts leave every creative decision inspectable on disk. @@ -207,7 +207,7 @@ For a 5-second one-shot animation, a single hand-authored composition is fine; t - The full website-to-video workflow built on this pipeline. + The full website-to-video guide built on this pipeline. How to invoke the pipeline through your AI agent. diff --git a/docs/guides/prompting.mdx b/docs/guides/prompting.mdx index fe78e6904..e74688ef3 100644 --- a/docs/guides/prompting.mdx +++ b/docs/guides/prompting.mdx @@ -32,8 +32,7 @@ The installer shows a picker. Select the **core skills** below — every project | Slash command | Input → output | | ------------------------ | --------------------------------------------------------------------------- | -| `/product-launch-video` | A product URL / brief / script → launch or promo video | -| `/website-to-video` | A general website / URL → a video of the site (tour / showcase / social clip) | +| `/product-launch-video` | Any website URL / brief / script → launch or promo video, or a site tour / showcase | | `/faceless-explainer` | Arbitrary text (no URL) → faceless explainer with its own TTS narration | | `/pr-to-video` | A GitHub PR → code-change explainer | | `/embedded-captions` | An existing talking-head video → the same footage with captions / subtitles | diff --git a/docs/guides/skills.mdx b/docs/guides/skills.mdx index c2b1030d2..aeb0082e8 100644 --- a/docs/guides/skills.mdx +++ b/docs/guides/skills.mdx @@ -7,7 +7,7 @@ HyperFrames ships AI agent skills via [vercel-labs/skills](https://github.com/ve The skills split into three groups: -- **Router** — the entry skill that picks a workflow for any "make me a…" request — usually a video, but also a navigable deck (`/slideshow`) or a composition port (`/remotion-to-hyperframes`). +- **Router** — the entry skill that confirms the brief up front (the intent layer) and picks a workflow for any "make me a…" request — usually a video, but also a navigable deck (`/slideshow`) or a composition port (`/remotion-to-hyperframes`). - **Creation workflows** — one per input shape (URL, PR, music track, captions, etc.). Each owns its task end-to-end: project setup, gated steps, and the final deliverable. Read by the router; you can also invoke directly if you already know which one fits. - **Domain skills** — atomic capabilities (animation, media, CLI, registry) the workflows compose against; they never own the end-to-end task. Load one when you need that specific layer. @@ -68,7 +68,7 @@ The single entry point. Read `/hyperframes` before invoking anything else — it | Skill | Use when | | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `/hyperframes` | **Read first** for any request to make / create / edit / animate / render a video, animation, or motion graphic. Capability map for the domain skills and intent router for the creation workflows below. | +| `/hyperframes` | **Read first** for any request to make / create / edit / animate / render a video, animation, or motion graphic. Capability map for the domain skills, the intent layer that confirms every creation brief up front, and intent router for the creation workflows below. | ## Creation workflows @@ -76,8 +76,7 @@ One workflow per input shape. The router (`/hyperframes`) picks one of these for | Skill | Use when | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `/product-launch-video` | Marketing / launching / promoting a **product** — from its URL, a brief, or a script (even if the site is only named). Up to ~3 min (sweet spot 30-90s). | -| `/website-to-video` | Turning a **general website** into a video — site tour, portfolio / landing-page showcase, social clip from the site's own visuals. | +| `/product-launch-video` | Any **website** — marketing / launching / promoting a product (from its URL, a brief, or a script), or a site tour / showcase / social clip featuring the site's own visuals. Up to ~3 min (sweet spot 30-90s). | | `/faceless-explainer` | **Explaining a topic / concept** from arbitrary text — no product, no URL, no website capture; every visual is LLM-invented (typography / abstract / diagram / data-viz). | | `/pr-to-video` | A **GitHub pull request** (PR URL, `owner/repo#N` ref, or "this PR") → changelog / feature-reveal / fix / refactor explainer, read via the `gh` CLI. | | `/embedded-captions` | Adding **captions / subtitles** to an existing talking-head video (footage untouched) — verbatim rail, embedded climax behind the subject, or pure-cinematic embed. | @@ -85,7 +84,7 @@ One workflow per input shape. The router (`/hyperframes`) picks one of these for | `/motion-graphics` | A short, **unnarrated, design-led motion graphic** (~under 10s) — kinetic type, stat / chart hit, logo sting, lower-third, animated tweet / headline. MP4 or transparent overlay. | | `/music-to-video` | A **music track** (audio file, or video to pull audio from) → a **beat-synced** video — lyric, slideshow, or kinetic promo; music drives pacing. | | `/slideshow` | A **presentation / pitch deck / interactive deck** — discrete slides, fragment reveals, branching, hotspot navigation, presenter mode. Output is a navigable deck, not a rendered video. | -| `/general-video` | **Anything else** — longer or multi-scene pieces, brand / sizzle reel, title card, static loop, freeform composition. Input- and length-agnostic fallback. | +| `/general-video` | **Anything else** — longer or multi-scene pieces, brand / sizzle reel, title card, static loop, freeform composition. Input- and length-agnostic fallback, and the home of companion mode (co-create with the full toolbox). | | `/remotion-to-hyperframes` | **Porting an existing Remotion** (React) composition's source to HyperFrames HTML. One-way migration, not creation. | ## Domain skills (loaded on demand) diff --git a/docs/guides/website-to-video.mdx b/docs/guides/website-to-video.mdx index fd530f9fb..b2f2215af 100644 --- a/docs/guides/website-to-video.mdx +++ b/docs/guides/website-to-video.mdx @@ -216,7 +216,7 @@ See the [pipeline guide](/guides/pipeline#iterating) for more re-entry patterns. npx skills add heygen-com/hyperframes ``` - Lead your prompt with _"Use the /website-to-video skill"_ for the most reliable results. Agents also discover it automatically when they see a URL and a video request. + Lead your prompt with _"Use the /product-launch-video skill"_ for the most reliable results — site tours route there too. Agents also discover it automatically when they see a URL and a video request. diff --git a/docs/launch-videos.mdx b/docs/launch-videos.mdx index 0c7e6fc9a..c4dcdd1b0 100644 --- a/docs/launch-videos.mdx +++ b/docs/launch-videos.mdx @@ -19,7 +19,7 @@ Each subdirectory is a standalone HyperFrames project — `index.html`, `composi | Project | What it shows | Source | | --- | --- | --- | | **HyperFrames launch** | The original announcement video — 49.7 s, glass-frame intro, CSS / GSAP / Lottie / shader / Three.js flex section, full narrative arc. | [`hyperframes-launch/`](https://github.com/heygen-com/hyperframes-launches/tree/main/hyperframes-launch) | -| **Website → HyperFrames** | Companion to the [website-to-video](/guides/website-to-video) workflow — captures a site and animates it. | [`website-to-hyperframes/`](https://github.com/heygen-com/hyperframes-launches/tree/main/website-to-hyperframes) | +| **Website → HyperFrames** | Companion to the [website-to-video](/guides/website-to-video) flow — now handled by `/product-launch-video` — captures a site and animates it. | [`website-to-hyperframes/`](https://github.com/heygen-com/hyperframes-launches/tree/main/website-to-hyperframes) | | **Timeline Editor launch** | 60 fps reveal video for the HyperFrames Timeline Editor: late-SFX cold open, chat-spiral of fine-tune prompts, editor reveal. | [`timeline-launch/`](https://github.com/heygen-com/hyperframes-launches/tree/main/timeline-launch) | | **Texture launch** | Texture-mask text + shader-driven background, used as a launch teaser. | [`texture-launch-video/`](https://github.com/heygen-com/hyperframes-launches/tree/main/texture-launch-video) | | **VFX × HeyGen combined** | Multi-act video chaining a VFX text-cursor scene with the HeyGen iPhone canvas test — useful as a reference for combining two existing projects into one render. | [`vfx-heygen-combined/`](https://github.com/heygen-com/hyperframes-launches/tree/main/vfx-heygen-combined) | diff --git a/docs/packages/cli.mdx b/docs/packages/cli.mdx index d6efc5c76..2ae1e4219 100644 --- a/docs/packages/cli.mdx +++ b/docs/packages/cli.mdx @@ -438,7 +438,7 @@ Word-level transcripts (whisper output) are grouped into readable caption cues o The capture command extracts everything an AI agent needs to understand a website's visual identity: viewport screenshots at every scroll depth, color palette (pixel-sampled + DOM computed), font files, images with semantic names, SVGs, Lottie animations, video previews, WebGL shaders, visible text, and page structure. - Output is a self-contained directory with a `CLAUDE.md` file that any AI agent can read to understand the captured site. Used by the `/website-to-video` skill as step 1 of the video production pipeline. + Output is a self-contained directory with a `CLAUDE.md` file that any AI agent can read to understand the captured site. Used by the `/product-launch-video` workflow as step 1 of the video production pipeline. Set `GEMINI_API_KEY` in a `.env` file for AI-powered image descriptions via Gemini vision (~$0.001/image), or set `OPENROUTER_API_KEY` to use any vision model through [OpenRouter](https://openrouter.ai) instead (takes priority if both are set; override the model with `HYPERFRAMES_OPENROUTER_MODEL`). See the [Website to Video](/guides/website-to-video#enriching-captures-with-gemini-vision) guide for details. diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index af6fb3e44..971c8c6a6 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -26,7 +26,7 @@ The installer shows a picker. Select the **core set** every project needs — th | `/hyperframes-registry` | Install catalog blocks and components | | `/general-video` | The general authoring workflow — the fallback for any video that doesn't match a specific workflow | -The **workflow skills** are optional — add the ones that match your inputs, and `/hyperframes` routes to whichever you've installed: `/product-launch-video`, `/website-to-video`, `/faceless-explainer`, `/pr-to-video`, `/embedded-captions`, `/talking-head-recut`, `/motion-graphics`, `/music-to-video`, `/slideshow`, `/general-video`, `/remotion-to-hyperframes`. See the [skills catalog](/guides/skills) for the full list with one-line "use when" descriptions. +The **workflow skills** are optional — add the ones that match your inputs, and `/hyperframes` routes to whichever you've installed: `/product-launch-video`, `/faceless-explainer`, `/pr-to-video`, `/embedded-captions`, `/talking-head-recut`, `/motion-graphics`, `/music-to-video`, `/slideshow`, `/general-video`, `/remotion-to-hyperframes`. See the [skills catalog](/guides/skills) for the full list with one-line "use when" descriptions. To skip the picker and install everything (core + every workflow) in one shot, run `npx skills add heygen-com/hyperframes --all`. diff --git a/packages/cli/src/capture/agentPromptGenerator.ts b/packages/cli/src/capture/agentPromptGenerator.ts index 9b6f33745..1f590f8de 100644 --- a/packages/cli/src/capture/agentPromptGenerator.ts +++ b/packages/cli/src/capture/agentPromptGenerator.ts @@ -7,7 +7,7 @@ * * This file generates a DATA INVENTORY that tells the AI agent what files * exist and what they contain. The actual workflow lives in the - * website-to-video skill — this file points agents there. + * product-launch-video skill — this file points agents there. */ import { writeFileSync, readdirSync, existsSync } from "node:fs"; @@ -198,7 +198,7 @@ function buildPrompt( Source: ${url} -To create a video from this capture, use the \`website-to-video\` skill. +To create a video from this capture, use the \`product-launch-video\` skill. ## What's in This Capture diff --git a/packages/cli/src/commands/preview.test.ts b/packages/cli/src/commands/preview.test.ts new file mode 100644 index 000000000..38b02f58e --- /dev/null +++ b/packages/cli/src/commands/preview.test.ts @@ -0,0 +1,53 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { studioLandingSearch } from "./preview.js"; + +const tempDirs: string[] = []; + +afterEach(() => { + for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true }); +}); + +function projectWith(storyboard: string | null, frameFiles: string[] = []): string { + const dir = mkdtempSync(join(tmpdir(), "hf-preview-landing-")); + tempDirs.push(dir); + if (storyboard !== null) writeFileSync(join(dir, "STORYBOARD.md"), storyboard); + for (const file of frameFiles) { + mkdirSync(join(dir, file, ".."), { recursive: true }); + writeFileSync(join(dir, file), "
"); + } + return dir; +} + +const FRAME = (n: number, status: string) => + `## Frame ${n} — F${n}\n- status: ${status}\n- src: compositions/frames/0${n}.html\n\nBeat.\n`; + +describe("studioLandingSearch", () => { + it("returns no search without a storyboard", () => { + expect(studioLandingSearch(projectWith(null))).toBe(""); + }); + + it("lands on the board while sketches are under review (any built frame)", () => { + const dir = projectWith(`${FRAME(1, "built")}${FRAME(2, "outline")}`, [ + "compositions/frames/01.html", + ]); + expect(studioLandingSearch(dir)).toBe("?view=storyboard"); + }); + + it("lands on the board during pure planning (srcs declared, none exist)", () => { + const dir = projectWith(`${FRAME(1, "outline")}${FRAME(2, "outline")}`); + expect(studioLandingSearch(dir)).toBe("?view=storyboard"); + }); + + it("lands on the timeline once frames exist without a built status", () => { + const dir = projectWith(`${FRAME(1, "outline")}`, ["compositions/frames/01.html"]); + expect(studioLandingSearch(dir)).toBe(""); + }); + + it("lands on the timeline for fully animated boards", () => { + const dir = projectWith(`${FRAME(1, "animated")}`, ["compositions/frames/01.html"]); + expect(studioLandingSearch(dir)).toBe(""); + }); +}); diff --git a/packages/cli/src/commands/preview.ts b/packages/cli/src/commands/preview.ts index 9e1ad7559..9879f0c51 100644 --- a/packages/cli/src/commands/preview.ts +++ b/packages/cli/src/commands/preview.ts @@ -22,7 +22,16 @@ export const examples: Example[] = [ ["List all active preview servers", "hyperframes preview --list"], ["Kill all active preview servers", "hyperframes preview --kill-all"], ]; -import { existsSync, lstatSync, symlinkSync, unlinkSync, readlinkSync, mkdirSync } from "node:fs"; +import { + existsSync, + lstatSync, + mkdirSync, + readFileSync, + readlinkSync, + symlinkSync, + unlinkSync, +} from "node:fs"; +import { parseStoryboard, STORYBOARD_FILENAME } from "@hyperframes/core/storyboard"; import { resolve, dirname, basename, join } from "node:path"; import { fileURLToPath } from "node:url"; import { createRequire } from "node:module"; @@ -368,7 +377,7 @@ export default defineCommand({ ], footer: `Stop with: hyperframes preview ${JSON.stringify(dir)} --stop`, }); - openStudioBrowser(url, projectName, { + openStudioBrowser(url, projectName, dir, { noOpen, browserPath, userDataDir, @@ -742,9 +751,47 @@ function compactSelectionPayload(selection: StudioSelectionSnapshot): CompactSel }; } -function openStudioBrowser(url: string, projectName: string, options?: BrowserLaunchOptions): void { +// Land the browser on the Storyboard view while the project is still planning +// or sketching — the timeline only becomes the right landing once frames are +// animated (or the storyboard never tracked statuses at all, e.g. beat plans). +export function studioLandingSearch(projectDir: string): string { + const storyboardPath = join(projectDir, STORYBOARD_FILENAME); + if (!existsSync(storyboardPath)) return ""; + let frames; + try { + frames = parseStoryboard(readFileSync(storyboardPath, "utf8")).frames; + } catch { + return ""; + } + // Sketch review in progress — the board is the review surface. + if (frames.some((f) => f.status === "built")) return "?view=storyboard"; + // Pure planning stage: frames declare src paths but none are built yet. + const srcs = frames + .map((f) => f.src) + .filter((s): s is string => typeof s === "string" && s.length > 0); + const planning = + frames.length > 0 && + frames.every((f) => f.status === "outline") && + srcs.length > 0 && + !srcs.some((s) => existsSync(join(projectDir, s))); + return planning ? "?view=storyboard" : ""; +} + +// The full Studio URL to open or hand to the user: status-aware landing view +// plus the project hash route. `url` never carries a trailing slash (both the +// embedded server and the Vite `Local:` match strip it). +function studioDeepLink(url: string, projectName: string, projectDir: string): string { + return `${url}/${studioLandingSearch(projectDir)}#project/${projectName}`; +} + +function openStudioBrowser( + url: string, + projectName: string, + projectDir: string, + options?: BrowserLaunchOptions, +): void { if (options?.noOpen) return; - openBrowser(`${url}#project/${projectName}`, { + openBrowser(studioDeepLink(url, projectName, projectDir), { browserPath: options?.browserPath, userDataDir: options?.userDataDir, remoteDebuggingPort: options?.remoteDebuggingPort, @@ -829,6 +876,7 @@ function attachStudioReadyHandler( child: StudioChildProcess, spinner: ReturnType, projectName: string, + projectDir: string, options?: BrowserLaunchOptions, ): void { let detected = false; @@ -840,7 +888,7 @@ function attachStudioReadyHandler( detected = true; spinner.stop(c.success("Studio running")); printStudioSummary(projectName, url, { footer: "Press Ctrl+C to stop" }); - openStudioBrowser(url, projectName, options); + openStudioBrowser(url, projectName, projectDir, options); child.stdout.removeListener("data", handleOutput); child.stderr.removeListener("data", handleOutput); } @@ -878,7 +926,7 @@ async function runDevMode(dir: string, options?: StudioLaunchOptions): Promise { + const project = await adapter.resolveProject(projectId); + if (!project) return null; + return { project, signature: resolveProjectSignature(adapter, project.dir) }; +} + /** * Creates a stable preview cache-busting signature for project source plus Studio manifests. */ diff --git a/packages/studio-server/src/routes/preview.ts b/packages/studio-server/src/routes/preview.ts index ebb522dcd..f9d399f34 100644 --- a/packages/studio-server/src/routes/preview.ts +++ b/packages/studio-server/src/routes/preview.ts @@ -7,7 +7,10 @@ import type { StudioApiAdapter } from "../types.js"; import { resolveWithinProject } from "../helpers/safePath.js"; import { getMimeType } from "../helpers/mime.js"; import { buildSubCompositionHtml } from "../helpers/subComposition.js"; -import { createProjectSignature } from "../helpers/projectSignature.js"; +import { + resolveProjectAndSignature, + resolveProjectSignature, +} from "../helpers/projectSignature.js"; import { createStudioMotionRenderBodyScript, STUDIO_MOTION_PATH, @@ -22,10 +25,6 @@ const GSAP_CDN_SCRIPT = ``; const GSAP_MOTION_PATH_CDN_SCRIPT = ``; -function resolveProjectSignature(adapter: StudioApiAdapter, projectDir: string): string { - return adapter.getProjectSignature?.(projectDir) ?? createProjectSignature(projectDir); -} - function injectProjectSignature(html: string, signature: string): string { const tag = ``; if (html.includes(`name="${PROJECT_SIGNATURE_META}"`)) { @@ -309,15 +308,15 @@ export function registerPreviewRoutes(api: Hono, adapter: StudioApiAdapter): voi // Bundled composition preview // fallow-ignore-next-line complexity api.get("/projects/:id/preview", async (c) => { - const project = await adapter.resolveProject(c.req.param("id")); - if (!project) return c.json({ error: "not found" }, 404); + const resolved = await resolveProjectAndSignature(adapter, c.req.param("id")); + if (!resolved) return c.json({ error: "not found" }, 404); + const { project, signature } = resolved; // fallow-ignore-next-line code-duplication const vars = previewVariablesFromRequest(c.req.query("variables")); if (vars.error !== undefined) return c.json({ error: vars.error }, 400); const previewVariables = vars.values; - const signature = resolveProjectSignature(adapter, project.dir); const etag = `"preview:${signature}${variablesEtagSalt(vars.raw)}"`; const ifNoneMatch = c.req.header("If-None-Match"); if (ifNoneMatch === etag) { @@ -422,15 +421,14 @@ export function registerPreviewRoutes(api: Hono, adapter: StudioApiAdapter): voi // Sub-composition preview // fallow-ignore-next-line complexity api.get("/projects/:id/preview/comp/*", async (c) => { - const project = await adapter.resolveProject(c.req.param("id")); - if (!project) return c.json({ error: "not found" }, 404); + const resolved = await resolveProjectAndSignature(adapter, c.req.param("id")); + if (!resolved) return c.json({ error: "not found" }, 404); + const { project, signature } = resolved; // fallow-ignore-next-line code-duplication const vars = previewVariablesFromRequest(c.req.query("variables")); if (vars.error !== undefined) return c.json({ error: vars.error }, 400); const previewVariables = vars.values; - - const signature = resolveProjectSignature(adapter, project.dir); const compPath = decodeURIComponent( c.req.path.replace(`/projects/${project.id}/preview/comp/`, "").split("?")[0] ?? "", ); diff --git a/packages/studio-server/src/routes/projects.test.ts b/packages/studio-server/src/routes/projects.test.ts index 7414048bd..e6a6fcfe3 100644 --- a/packages/studio-server/src/routes/projects.test.ts +++ b/packages/studio-server/src/routes/projects.test.ts @@ -58,6 +58,50 @@ function createAdapter(projectDir: string): StudioApiAdapter { }; } +describe("GET /projects/:id/signature", () => { + it("returns the adapter's cached signature when provided", async () => { + const projectDir = createProjectDir(); + const app = new Hono(); + registerProjectRoutes(app, { + ...createAdapter(projectDir), + getProjectSignature: () => "cached-sig", + }); + + const response = await app.request("http://localhost/projects/demo/signature"); + expect(response.status).toBe(200); + expect(await response.json()).toEqual({ signature: "cached-sig" }); + }); + + it("computes a signature that moves when project files change", async () => { + const projectDir = createProjectDir(); + const app = new Hono(); + registerProjectRoutes(app, createAdapter(projectDir)); + + const first = (await ( + await app.request("http://localhost/projects/demo/signature") + ).json()) as { + signature: string; + }; + expect(first.signature).toMatch(/^[0-9a-f]{24}$/); + + writeFileSync(join(projectDir, "compositions", "scene.html"), "new"); + const second = (await ( + await app.request("http://localhost/projects/demo/signature") + ).json()) as { signature: string }; + expect(second.signature).not.toBe(first.signature); + }); + + it("404s for an unknown project", async () => { + const app = new Hono(); + registerProjectRoutes(app, { + ...createAdapter(createProjectDir()), + resolveProject: async () => null, + }); + const response = await app.request("http://localhost/projects/nope/signature"); + expect(response.status).toBe(404); + }); +}); + describe("registerProjectRoutes — composition discovery (#1384)", () => { it("excludes HTML inside dot-directories from compositions", async () => { const projectDir = createProjectDir(); diff --git a/packages/studio-server/src/routes/projects.ts b/packages/studio-server/src/routes/projects.ts index 76fb0079e..b0ca6ad2a 100644 --- a/packages/studio-server/src/routes/projects.ts +++ b/packages/studio-server/src/routes/projects.ts @@ -3,6 +3,7 @@ import { join } from "node:path"; import type { Hono } from "hono"; import type { StudioApiAdapter } from "../types.js"; import { isInHiddenOrVendorDir, walkDir } from "../helpers/safePath.js"; +import { resolveProjectSignature } from "../helpers/projectSignature.js"; const COMPOSITION_ID_RE = /data-composition-id\s*=/; @@ -39,6 +40,15 @@ export function registerProjectRoutes(api: Hono, adapter: StudioApiAdapter): voi return c.json(result); }); + // Current content signature for a project — a cheap poll target for clients + // that refresh themselves when files change on disk (the storyboard board + // re-fetches when this differs from the signature its data was loaded with). + api.get("/projects/:id/signature", async (c) => { + const project = await adapter.resolveProject(c.req.param("id")); + if (!project) return c.json({ error: "not found" }, 404); + return c.json({ signature: resolveProjectSignature(adapter, project.dir) }); + }); + // Project file tree api.get("/projects/:id", async (c) => { const project = await adapter.resolveProject(c.req.param("id")); diff --git a/packages/studio-server/src/routes/storyboard.test.ts b/packages/studio-server/src/routes/storyboard.test.ts index 94f040c37..a2fd53523 100644 --- a/packages/studio-server/src/routes/storyboard.test.ts +++ b/packages/studio-server/src/routes/storyboard.test.ts @@ -106,4 +106,17 @@ Not built yet. const { body } = await getStoryboard(dir); expect(body.frames[0].srcExists).toBe(false); }); + + it("carries a project signature in both the absent and present branches", async () => { + const dir = makeProject(); + const absent = await getStoryboard(dir); + expect(absent.body.exists).toBe(false); + expect(absent.body.signature).toMatch(/^[0-9a-f]{24}$/); + + writeFileSync(join(dir, "STORYBOARD.md"), "## Frame 1\n\nHi.\n"); + const present = await getStoryboard(dir); + expect(present.body.exists).toBe(true); + expect(present.body.signature).toMatch(/^[0-9a-f]{24}$/); + expect(present.body.signature).not.toBe(absent.body.signature); + }); }); diff --git a/packages/studio-server/src/routes/storyboard.ts b/packages/studio-server/src/routes/storyboard.ts index 874204208..519463b29 100644 --- a/packages/studio-server/src/routes/storyboard.ts +++ b/packages/studio-server/src/routes/storyboard.ts @@ -2,6 +2,7 @@ import { existsSync, readFileSync } from "node:fs"; import type { Hono } from "hono"; import type { StudioApiAdapter } from "../types.js"; import { resolveWithinProject } from "../helpers/safePath.js"; +import { resolveProjectAndSignature } from "../helpers/projectSignature.js"; import { parseStoryboard, SCRIPT_FILENAME, @@ -45,8 +46,11 @@ export function registerStoryboardRoutes(api: Hono, adapter: StudioApiAdapter): // file is absent we return `exists: false` with empty frames rather than 404, // so the Studio can render an opt-in empty state. api.get("/projects/:id/storyboard", async (c) => { - const project = await adapter.resolveProject(c.req.param("id")); - if (!project) return c.json({ error: "not found" }, 404); + // The signature lets the board bust poster caches and lets the client tell + // whether this payload is already current (see /projects/:id/signature). + const resolved = await resolveProjectAndSignature(adapter, c.req.param("id")); + if (!resolved) return c.json({ error: "not found" }, 404); + const { project, signature } = resolved; const abs = resolveWithinProject(project.dir, STORYBOARD_FILENAME); if (!abs || !existsSync(abs)) { @@ -57,6 +61,7 @@ export function registerStoryboardRoutes(api: Hono, adapter: StudioApiAdapter): frames: [], warnings: [], script: readScript(project.dir), + signature, }); } @@ -75,6 +80,7 @@ export function registerStoryboardRoutes(api: Hono, adapter: StudioApiAdapter): frames: resolveFrames(project.dir, manifest.frames), warnings: manifest.warnings, script: readScript(project.dir), + signature, }); }); } diff --git a/packages/studio/src/components/storyboard/FramePoster.tsx b/packages/studio/src/components/storyboard/FramePoster.tsx index 722f3dbd6..60d41e044 100644 --- a/packages/studio/src/components/storyboard/FramePoster.tsx +++ b/packages/studio/src/components/storyboard/FramePoster.tsx @@ -10,6 +10,13 @@ export interface FramePosterProps { title: string; /** `cover` fills+crops (contact-sheet tile); `contain` letterboxes (focus hero). */ fit?: "cover" | "contain"; + /** + * Project content signature to key the poster URL on. The thumbnail route + * regenerates when the frame's source changes, but the browser only refetches + * when the URL does — so bake the signature in to pick up fresh posters after + * a background board refresh. + */ + posterVersion?: string; } /** @@ -18,11 +25,19 @@ export interface FramePosterProps { * no postMessage seek, and no client-side fps assumption. Shared by the * contact-sheet tile and the frame-focus view. */ -export function FramePoster({ projectId, src, seconds, title, fit = "cover" }: FramePosterProps) { +export function FramePoster({ + projectId, + src, + seconds, + title, + fit = "cover", + posterVersion, +}: FramePosterProps) { const [failed, setFailed] = useState(false); // The is reused (no key) when a tile/hero swaps to a different frame, so a - // prior load error would stick. Reset when the poster target changes. - useEffect(() => setFailed(false), [src, seconds]); + // prior load error would stick. Reset when the poster target changes — including + // a new posterVersion, so a frame that failed mid-write retries once it settles. + useEffect(() => setFailed(false), [src, seconds, posterVersion]); if (failed) { return (
@@ -30,12 +45,17 @@ export function FramePoster({ projectId, src, seconds, title, fit = "cover" }: F
); } - const url = buildCompositionThumbnailUrl({ + let url = buildCompositionThumbnailUrl({ previewUrl: `/api/projects/${projectId}/preview/comp/${src}`, seekTime: seconds, duration: 0, origin: window.location.origin, }); + if (posterVersion) { + const withVersion = new URL(url, window.location.origin); + withVersion.searchParams.set("sig", posterVersion); + url = withVersion.toString(); + } return ( void; /** Select a composition in the timeline (sets active comp + editing file + sidebar highlight). */ onSelectComposition: (path: string) => void; + /** Project signature the board was loaded with (busts the poster cache). */ + posterVersion?: string; } /** @@ -39,6 +41,7 @@ export function StoryboardFrameFocus({ onNavigate, onSaved, onSelectComposition, + posterVersion, }: StoryboardFrameFocusProps) { const { readProjectFile, writeProjectFile } = useFileManagerContext(); const { setViewMode } = useViewMode(); @@ -151,6 +154,7 @@ export function StoryboardFrameFocus({ seconds={posterTime(frame)} title={title} fit="contain" + posterVersion={posterVersion} /> ) : (
diff --git a/packages/studio/src/components/storyboard/StoryboardFrameTile.tsx b/packages/studio/src/components/storyboard/StoryboardFrameTile.tsx index 62b0173bf..a128a6fbe 100644 --- a/packages/studio/src/components/storyboard/StoryboardFrameTile.tsx +++ b/packages/studio/src/components/storyboard/StoryboardFrameTile.tsx @@ -7,6 +7,13 @@ export interface StoryboardFrameTileProps { frame: StoryboardFrameView; /** Open this frame in the full-area focus view. */ onOpen: (index: number) => void; + /** This frame's pending comment draft ("" when none). */ + commentDraft: string; + onCommentDraftChange: (index: number, text: string) => void; + /** A submitted comment the agent has not consumed yet (null when none). */ + pendingComment: string | null; + /** Project signature the board was loaded with (busts the poster cache). */ + posterVersion?: string; } function firstLine(text: string): string { @@ -26,7 +33,15 @@ function placeholderMessage(frame: StoryboardFrameView): string { /** A single contact-sheet tile: poster preview + its metadata. Click to focus. */ // fallow-ignore-next-line complexity -export function StoryboardFrameTile({ projectId, frame, onOpen }: StoryboardFrameTileProps) { +export function StoryboardFrameTile({ + projectId, + frame, + onOpen, + commentDraft, + onCommentDraftChange, + pendingComment, + posterVersion, +}: StoryboardFrameTileProps) { const meta = FRAME_STATUS_META[frame.status]; const renderable = frame.srcExists && frame.status !== "outline"; const title = frame.title ?? `Frame ${frame.index}`; @@ -48,6 +63,7 @@ export function StoryboardFrameTile({ projectId, frame, onOpen }: StoryboardFram src={frame.src} seconds={posterTime(frame)} title={title} + posterVersion={posterVersion} /> ) : ( @@ -74,6 +90,19 @@ export function StoryboardFrameTile({ projectId, frame, onOpen }: StoryboardFram {frame.duration && {frame.duration}} {frame.transitionIn && ↘ {frame.transitionIn}}
+