Skip to content

Commit 8e1ea7a

Browse files
committed
feat(studio): marquee selection, AE presets, per-keyframe ease + velocity fitting
Marquee selection: click+drag on empty canvas draws a dashed selection rectangle. All elements whose OBB intersects are group-selected via SAT. Shift+marquee adds to selection. Click on empty deselects. Per-keyframe easing: each keyframe segment has its own ease, editable via expandable bezier curve editor in the Animation panel. Parser preserves per-keyframe ease through round-trips (ease-only updates preserve existing properties). AE Easy Ease presets: correct After Effects bezier values (0.333, 0, 0.667, 1) in the preset grid. Velocity-based curve fitting: gesture recordings analyze velocity profile and assign per-keyframe custom eases automatically. Gesture smoothing: Gaussian-weighted moving average (from PR #1658) + easeEach support for keyframed tweens + fetch-cancellation race fix in useGsapAnimationsForElement.
1 parent 20d7200 commit 8e1ea7a

95 files changed

Lines changed: 1992 additions & 564 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "hyperframes",
33
"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.",
4-
"version": "0.7.3",
4+
"version": "0.7.4",
55
"author": {
66
"name": "HeyGen",
77
"email": "hyperframes@heygen.com",

.codex-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hyperframes",
3-
"version": "0.7.3",
3+
"version": "0.7.4",
44
"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.",
55
"author": {
66
"name": "HeyGen",

.cursor-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"$schema": "https://cursor.com/schemas/cursor-plugin/plugin.json",
33
"name": "hyperframes",
44
"displayName": "HyperFrames by HeyGen",
5-
"version": "0.7.3",
5+
"version": "0.7.4",
66
"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.",
77
"author": {
88
"name": "HeyGen",

docs/changelog.mdx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,60 @@ Recent HyperFrames releases, including user-facing features, fixes, and migratio
88

99
{/* New release entries are prepended by `bun run changelog:draft <version> --write`. */}
1010

11+
<Update
12+
label="HyperFrames v0.7.4"
13+
description="Released - 2026-06-24"
14+
tags={["Release", "Producer", "Core", "Studio"]}
15+
>
16+
This stable release improves render reliability and skill authoring: producer duration
17+
now falls back to sub-composition timing, empty sub-composition files no longer abort
18+
renders, and Studio timeline styling handles missing tags safely. It also ships
19+
music-to-video workflow updates, per-skill telemetry, and clearer CLI install/debug
20+
guidance.
21+
22+
## Features
23+
24+
- **CLI:** Per-skill usage telemetry ([4961e4e4](https://github.com/heygen-com/hyperframes/commit/4961e4e4770a9b38eb34fef957c0ac8764614ce8))
25+
- **Skills:** Unify bgm-to-video flows into music-to-video ([a34d3dba](https://github.com/heygen-com/hyperframes/commit/a34d3dba4d15b3d6830bc546b7b6c423a1304477))
26+
- **Skills:** Add bgm-to-video skill ([d596379e](https://github.com/heygen-com/hyperframes/commit/d596379e52536672366e889602004a65bc8136d9))
27+
- **Skills:** Guide install when a matched workflow isn't installed ([08328b04](https://github.com/heygen-com/hyperframes/commit/08328b04e63e16c7bea469237552c1d9587dcfec), [#1647](https://github.com/heygen-com/hyperframes/pull/1647))
28+
29+
## Fixes
30+
31+
- **Producer:** Derive duration from sub-composition timing when root has no data-duration ([45a9440c](https://github.com/heygen-com/hyperframes/commit/45a9440c61073314dd8d3ac34813d108c1e051e7), [#1680](https://github.com/heygen-com/hyperframes/pull/1680))
32+
- **Core:** Skip empty sub-composition files instead of aborting render ([656c1200](https://github.com/heygen-com/hyperframes/commit/656c1200ee2e42fe2affa9641af8fcf0ccbb5683), [#1678](https://github.com/heygen-com/hyperframes/pull/1678))
33+
- **Studio:** Guard against null tag in timeline track style ([d4aa5f93](https://github.com/heygen-com/hyperframes/commit/d4aa5f93e16c564d5248da99cd22101b314d3d63), [#1679](https://github.com/heygen-com/hyperframes/pull/1679))
34+
- **CLI:** Improve whisper-cpp install guidance and add doctor check ([344618e2](https://github.com/heygen-com/hyperframes/commit/344618e22aaf27b526d16ef219dfe193ae8920fd), [#1681](https://github.com/heygen-com/hyperframes/pull/1681))
35+
- **Engine:** Restore fast screenshot path for viewport captures ([f622e5a7](https://github.com/heygen-com/hyperframes/commit/f622e5a7ba3f840bda2703a93784ea0105d21fef), [#1670](https://github.com/heygen-com/hyperframes/pull/1670))
36+
- **CLI:** Install skills into project dir on non-interactive init ([bf4f34b3](https://github.com/heygen-com/hyperframes/commit/bf4f34b359c252bfac25061d19bd178b28da5588), [#1671](https://github.com/heygen-com/hyperframes/pull/1671))
37+
- **CLI:** Expose render debug mode ([7133c396](https://github.com/heygen-com/hyperframes/commit/7133c396eb29f4d28f3d25b8dc8dcf017bc70618))
38+
- Unblock music video ci checks ([5d1bff51](https://github.com/heygen-com/hyperframes/commit/5d1bff51b5a21145b9d84c95d21447ebb90efc13))
39+
- **Producer:** Store css-var-fonts baseline as raw binary, not LFS pointer ([119284d3](https://github.com/heygen-com/hyperframes/commit/119284d377878e38df60469a6bbeaf6d6a8ed857))
40+
- **Producer:** Restore css-var-fonts regression baseline ([f012b8b8](https://github.com/heygen-com/hyperframes/commit/f012b8b84691c6a5989e11c0d228c5320d8d6843))
41+
- **Lint:** Catch CSS↔GSAP transform conflicts in scoped selectors and frame sub-compositions ([9ccae863](https://github.com/heygen-com/hyperframes/commit/9ccae8637291262b8a2e1a1779e4ee7ad186f675))
42+
43+
## Docs & Examples
44+
45+
- **Skills:** Add music-source brief to music-to-video Step 0 ([7a464689](https://github.com/heygen-com/hyperframes/commit/7a4646894f0d3d5edcc185f1e1a61dcce11345ed))
46+
- **Skills:** Register music-to-video in the hyperframes router ([44b04324](https://github.com/heygen-com/hyperframes/commit/44b04324b1576d80a2fb5a1bdf8dda0fd049a6ff))
47+
- **Skills:** Add beat-synced montage authoring recipe ([0dcf914e](https://github.com/heygen-com/hyperframes/commit/0dcf914e14ae74ddfe6e8c6118113929bcfb894f))
48+
49+
## Catalog
50+
51+
- Refine music-to-video planning catalogs ([a23ca348](https://github.com/heygen-com/hyperframes/commit/a23ca3487f712d9df9ed72e1d6b16683dfc4bd2f))
52+
53+
## Internal
54+
55+
- **Lint:** Keep the fallow audit gate green ([4a8e2f1c](https://github.com/heygen-com/hyperframes/commit/4a8e2f1cd0a1d156ac485fd084230da8c407b152))
56+
57+
## Other Changes
58+
59+
- Merge pull request #1672 from heygen-com/fix/skill-authoring-fixes ([26810856](https://github.com/heygen-com/hyperframes/commit/2681085624056aeac851b03fd46efe512017c2a7))
60+
- **Skills:** Apply oxfmt to music-to-video and router docs ([265b0273](https://github.com/heygen-com/hyperframes/commit/265b02738e35d21b5455a640323551f0e3cf3c6e))
61+
62+
[View the full commit range](https://github.com/heygen-com/hyperframes/compare/v0.7.3...v0.7.4).
63+
</Update>
64+
1165
<Update
1266
label="HyperFrames v0.7.3"
1367
description="Released - 2026-06-23"

packages/aws-lambda/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@hyperframes/aws-lambda",
3-
"version": "0.7.3",
3+
"version": "0.7.4",
44
"description": "AWS Lambda adapter for HyperFrames distributed rendering — handler, client-side SDK, and CDK construct.",
55
"repository": {
66
"type": "git",

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@hyperframes/cli",
3-
"version": "0.7.3",
3+
"version": "0.7.4",
44
"description": "HyperFrames CLI — create, preview, and render HTML video compositions",
55
"repository": {
66
"type": "git",

packages/cli/src/cli.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ const commandLoaders = {
135135
skills: () => import("./commands/skills.js").then((m) => m.default),
136136
feedback: () => import("./commands/feedback.js").then((m) => m.default),
137137
telemetry: () => import("./commands/telemetry.js").then((m) => m.default),
138+
events: () => import("./commands/events.js").then((m) => m.default),
138139
validate: () => import("./commands/validate.js").then((m) => m.default),
139140
snapshot: () => import("./commands/snapshot.js").then((m) => m.default),
140141
capture: () => import("./commands/capture.js").then((m) => m.default),
@@ -194,7 +195,10 @@ let _trackCommandResult:
194195
| undefined;
195196
let _printUpdateNotice: (() => void) | undefined;
196197

197-
if (!isHelp && command !== "telemetry" && command !== "unknown") {
198+
// `events` is a telemetry-internal beacon: it self-tracks + self-flushes, so it
199+
// skips the per-command wrapper (no duplicate cli_command, no first-run notice
200+
// printed into a skill's captured output).
201+
if (!isHelp && command !== "telemetry" && command !== "events" && command !== "unknown") {
198202
import("./telemetry/index.js").then((mod) => {
199203
_flush = mod.flush;
200204
_flushSync = mod.flushSync;
@@ -206,7 +210,9 @@ if (!isHelp && command !== "telemetry" && command !== "unknown") {
206210
});
207211
}
208212

209-
if (!isHelp && !hasJsonFlag && command !== "upgrade") {
213+
// `events` skips the update check too — a skill-usage beacon must not add
214+
// network latency or trigger a background self-upgrade on the calling skill.
215+
if (!isHelp && !hasJsonFlag && command !== "upgrade" && command !== "events") {
210216
// Report any completed auto-install from the previous run first, before
211217
// kicking off the next check — so the user sees "updated to vX" once and
212218
// we don't over-print.

packages/cli/src/commands/doctor.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,19 @@ function checkEnvironment(): CheckResult {
145145
return { ok: true, detail: parts.join(" \u00B7 ") };
146146
}
147147

148+
async function checkWhisper(): Promise<CheckResult> {
149+
const { findWhisper, getInstallInstructions } = await import("../whisper/manager.js");
150+
const result = findWhisper();
151+
if (result) {
152+
return { ok: true, detail: result.executablePath };
153+
}
154+
return {
155+
ok: false,
156+
detail: "Not found (optional \u2014 needed for transcription)",
157+
hint: getInstallInstructions(),
158+
};
159+
}
160+
148161
export interface CheckOutcome {
149162
name: string;
150163
ok: boolean;
@@ -213,6 +226,7 @@ export default defineCommand({
213226
}
214227

215228
checks.push({ name: "Environment", run: checkEnvironment });
229+
checks.push({ name: "whisper-cpp", run: checkWhisper });
216230

217231
const outcomes: CheckOutcome[] = [];
218232
for (const check of checks) {
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { defineCommand } from "citty";
2+
import { trackEvent, flush } from "../telemetry/client.js";
3+
4+
// Skill-usage telemetry endpoint. A skill reports its own invocation/outcome —
5+
// ideally from its own bundled script, so it fires deterministically rather
6+
// than relying on the agent to remember:
7+
//
8+
// npx hyperframes events --skill=product-launch-video
9+
// npx hyperframes events --skill=product-launch-video --event=skill_completed --outcome=success
10+
//
11+
// Rides the SAME anonymous PostHog pipeline + consent gates as every other CLI
12+
// event (DO_NOT_TRACK / telemetry opt-out, anonymous install UUID, IP stripped).
13+
//
14+
// Telemetry must NEVER break the calling skill: every arg is optional (a missing
15+
// or malformed value is a silent no-op, not a non-zero exit), the body is
16+
// guarded, and flush() carries its own hard timeout. This command always exits 0.
17+
18+
const ALLOWED_EVENTS = ["skill_invoked", "skill_completed"];
19+
const ALLOWED_OUTCOMES = ["success", "error", "abort"];
20+
// Skill names are lowercase slugs (e.g. "product-launch-video"). Anything that
21+
// doesn't match is dropped, so a caller can't push high-cardinality or PII
22+
// strings (paths, shell output, free text) into the anonymous event stream.
23+
const SKILL_SLUG = /^[a-z0-9][a-z0-9-]{0,63}$/;
24+
25+
export default defineCommand({
26+
meta: {
27+
name: "events",
28+
description:
29+
"Emit an anonymous skill-usage telemetry event (skills report their own invocation/outcome). Honors DO_NOT_TRACK / telemetry opt-out.",
30+
},
31+
args: {
32+
skill: {
33+
type: "string",
34+
description: "Authoring skill slug, e.g. product-launch-video",
35+
},
36+
event: {
37+
type: "string",
38+
description: "Event name: skill_invoked | skill_completed (default: skill_invoked)",
39+
default: "skill_invoked",
40+
},
41+
outcome: {
42+
type: "string",
43+
description: "Optional outcome for completion events: success | error | abort",
44+
},
45+
},
46+
async run({ args }) {
47+
// Best-effort: nothing here may fail the skill that called us. Missing or
48+
// malformed input is a silent no-op rather than a non-zero exit.
49+
try {
50+
const skill = typeof args.skill === "string" ? args.skill.trim() : "";
51+
if (!SKILL_SLUG.test(skill)) return; // missing / non-slug → no-op
52+
53+
const event = ALLOWED_EVENTS.includes(args.event) ? args.event : "skill_invoked";
54+
const props: Record<string, string> = { authoring_skill: skill };
55+
if (args.outcome && ALLOWED_OUTCOMES.includes(args.outcome)) {
56+
props["outcome"] = args.outcome;
57+
}
58+
trackEvent(event, props);
59+
await flush();
60+
} catch {
61+
// swallow — telemetry must never surface a non-zero exit to the caller
62+
}
63+
},
64+
});

packages/cli/src/commands/init.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -800,11 +800,27 @@ export default defineCommand({
800800
for (const f of readdirSync(destDir).filter((f) => !f.startsWith("."))) {
801801
console.log(` ${c.accent(f)}`);
802802
}
803+
804+
if (!skipSkills) {
805+
const { installAllSkills } = await import("./skills.js");
806+
// --yes keeps it non-interactive. When Claude Code is driving
807+
// (CLAUDECODE env var), target its native dir so skills land in
808+
// .claude/skills/ instead of only .agents/skills/.
809+
const args = process.env["CLAUDECODE"] ? ["--agent", "claude-code", "--yes"] : ["--yes"];
810+
await installAllSkills({ cwd: destDir, extraArgs: args });
811+
}
812+
803813
console.log();
804814
console.log("Get started:");
805815
console.log();
806-
console.log(` ${c.accent("1.")} Install AI coding skills (one-time):`);
807-
console.log(` ${c.accent("npx skills add heygen-com/hyperframes")}`);
816+
if (skipSkills) {
817+
console.log(` ${c.accent("1.")} Install AI coding skills (one-time):`);
818+
console.log(` ${c.accent("npx skills add heygen-com/hyperframes --yes")}`);
819+
} else {
820+
console.log(
821+
` ${c.accent("1.")} Restart your AI agent (new session) so it loads the skills.`,
822+
);
823+
}
808824
console.log();
809825
console.log(` ${c.accent("2.")} Open this project with your AI coding agent:`);
810826
console.log(
@@ -1018,8 +1034,8 @@ export default defineCommand({
10181034
process.exit(0);
10191035
}
10201036
if (installSkills) {
1021-
const skillsCmd = await import("./skills.js").then((m) => m.default);
1022-
await runCommand(skillsCmd, { rawArgs: [] });
1037+
const { installAllSkills } = await import("./skills.js");
1038+
await installAllSkills({ cwd: destDir });
10231039
}
10241040
}
10251041

0 commit comments

Comments
 (0)