diff --git a/.fallowrc.jsonc b/.fallowrc.jsonc index 75d0064561..a0130b782f 100644 --- a/.fallowrc.jsonc +++ b/.fallowrc.jsonc @@ -202,15 +202,16 @@ "file": "packages/studio/src/utils/timelineElementSplit.ts", "exports": ["buildPatchTarget", "readFileContent"], }, - // freezeUrl and freezeLocalFile are public API for Task 4 manifest flow - // and the /figma skill integration; not yet imported by current code. + // freezeUrl and freezeLocalFile are public API re-exported from the figma + // barrel (index.ts) for Task 4 manifest flow and the /figma skill integration; + // not yet imported by current code outside the module. { "file": "packages/core/src/figma/freeze.ts", "exports": ["freezeUrl", "freezeLocalFile"], }, - // mediaDir, typeDirPath, isFigmaManifestRecord: exported from manifest.ts - // for barrel wiring in Task 8 (one-batch export across Tasks 2-7). - // Not yet consumed by any code in the current stack. + // mediaDir, typeDirPath, isFigmaManifestRecord: re-exported from the figma + // barrel (index.ts) via manifest.ts per Task 8 wiring. Consumed only by the + // /figma skill integration, not by code in the current codebase. { "file": "packages/core/src/figma/manifest.ts", "exports": ["mediaDir", "typeDirPath", "isFigmaManifestRecord"], diff --git a/CLAUDE.md b/CLAUDE.md index 948707a14f..74f78933df 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,11 +4,11 @@ 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 21 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 # interactive picker -npx skills add heygen-com/hyperframes --all # install all 20 (skips picker) +npx skills add heygen-com/hyperframes --all # install all 21 (skips picker) npx skills add heygen-com/hyperframes --skill # just one (bare name, no leading slash) ``` @@ -40,6 +40,7 @@ Atomic capabilities the creation workflows compose against — pull one when you - `/media-use` — resolve any media need (BGM, SFX, image, icon) into a frozen local file + ledger record. One verb (`resolve`) over the HeyGen catalog with manifest tracking; keeps search noise on disk. - `/hyperframes-cli` — CLI dev loop: `init`, `add`, `lint`, `validate`, `inspect`, `preview`, `render`, `publish`, `doctor`, `lambda` (AWS Lambda cloud rendering). - `/hyperframes-registry` — install and wire registry blocks and components into compositions via `hyperframes add`. Covers authoring a new block or component to contribute upstream. +- `/figma` — import Figma assets, tokens, components, and Motion animations into a composition (MCP-first). ## Skill catalog maintenance @@ -48,7 +49,7 @@ When adding a new skill, or substantially renaming / repurposing an existing one 1. The skill list above (CLAUDE.md) 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. 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. Mirror the Router / Creation workflows / Domain skills grouping across all three surfaces so a skill always lives in the same column. -4. 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 deliberately omits a count to avoid drift; keep it count-free. +4. Skill count appears in the README and CLAUDE.md intro lines ("21 AI agent skills…") — update on add/remove. The `docs/guides/skills.mdx` page deliberately omits a count to avoid drift; keep it 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 321d06f404..b9061da564 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,9 @@ 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 video" request and points to the domain skills below. +HyperFrames ships 21 skills agents load on demand. Read `/hyperframes` first — it's the router and capability map; it picks a workflow for any "make me a video" request and points to the domain skills below. -Run `npx skills add heygen-com/hyperframes` for the interactive picker, `npx skills add heygen-com/hyperframes --all` to install all 20 at once (skips the picker), or `npx skills add heygen-com/hyperframes --skill ` for just one (bare name, no leading `/`). +Run `npx skills add heygen-com/hyperframes` for the interactive picker, `npx skills add heygen-com/hyperframes --all` to install all 21 at once (skips the picker), or `npx skills add heygen-com/hyperframes --skill ` for just one (bare name, no leading `/`). ### Router @@ -89,6 +89,7 @@ Atomic capabilities the creation workflows compose against — pull one when you | `/media-use` | Resolve any media need (BGM, SFX, image, icon) into a frozen local file + ledger record. One verb (`resolve`) over the HeyGen catalog with manifest tracking. | | `/hyperframes-cli` | CLI dev loop — `init`, `lint`, `validate`, `inspect`, `preview`, `render`, `publish`, `doctor`, plus AWS Lambda cloud rendering (`lambda deploy / render / progress`). | | `/hyperframes-registry` | Install and wire registry blocks and components into compositions via `hyperframes add`. Authoring a new block or component to contribute upstream. | +| `/figma` | Import Figma assets, tokens, components, and Motion animations into a composition (MCP-first). | For visual design handoff workflows, see the [Claude Design guide](https://hyperframes.heygen.com/guides/claude-design) and [Open Design guide](https://hyperframes.heygen.com/guides/open-design). diff --git a/docs/guides/skills.mdx b/docs/guides/skills.mdx index 4d592463b4..57c8ea8c35 100644 --- a/docs/guides/skills.mdx +++ b/docs/guides/skills.mdx @@ -84,6 +84,7 @@ Atomic capabilities the creation workflows compose against — pull one when you | `/media-use` | Resolve any media need (BGM, SFX, image, icon) into a frozen local file + ledger record. One verb (`resolve`) over the HeyGen catalog with manifest tracking. | | `/hyperframes-cli` | CLI dev loop — `init`, `lint`, `validate`, `inspect`, `preview`, `render`, `publish`, `doctor`, plus AWS Lambda cloud rendering (`lambda deploy / render / progress`). | | `/hyperframes-registry` | Install and wire registry blocks and components into compositions via `hyperframes add`. Authoring a new block or component to contribute upstream. | +| `/figma` | Import Figma assets, tokens, components, and Motion animations into a composition (MCP-first). | ## Source of truth diff --git a/packages/cli/src/server/studioRenderTelemetry.test.ts b/packages/cli/src/server/studioRenderTelemetry.test.ts index c44fa6f0fc..17e7114568 100644 --- a/packages/cli/src/server/studioRenderTelemetry.test.ts +++ b/packages/cli/src/server/studioRenderTelemetry.test.ts @@ -109,6 +109,10 @@ const fullPerf: RenderPerfSummary = { extractMs: 60, cacheHits: 3, cacheMisses: 4, + cachePublishFailures: 0, + cacheGcEvictions: 0, + cacheGcBytesFreed: 0, + cacheAgedPartialsCleared: 0, }, tmpPeakBytes: 1024, captureAvgMs: 13, diff --git a/packages/core/src/figma/emitTimelineScript.test.ts b/packages/core/src/figma/emitTimelineScript.test.ts new file mode 100644 index 0000000000..c9a24c60f0 --- /dev/null +++ b/packages/core/src/figma/emitTimelineScript.test.ts @@ -0,0 +1,48 @@ +// @vitest-environment node +import { describe, expect, it } from "vitest"; +import { emitTimelineScript } from "./emitTimelineScript"; +import { motionToGsap } from "./motionToGsap"; +import type { MotionDoc } from "./types"; + +const doc: MotionDoc = { + selector: "#hero-headline", + tracks: [ + { + property: "opacity", + values: [0, 1, 0], + times: [0, 0.5, 1], + ease: ["linear", [0.539, 0, 0.312, 0.995]], + duration: 2, + repeat: Infinity, + }, + ], +}; + +describe("emitTimelineScript", () => { + const script = emitTimelineScript(motionToGsap(doc)); + + it("creates a paused timeline and never emits repeat:-1", () => { + expect(script).toContain("gsap.timeline({ paused: true })"); + expect(script).not.toContain("repeat: -1"); + }); + it("registers under a string-literal __timelines key", () => { + expect(script).toContain('window.__timelines["figma-hero-headline"] = tl;'); + }); + it("uses string-literal selectors and sets the initial value", () => { + expect(script).toContain('tl.set("#hero-headline", { opacity: 0 }, 0);'); + expect(script).toContain('tl.to("#hero-headline", { keyframes: ['); + }); + it("registers a CustomEase for the bezier segment", () => { + expect(script).toContain('CustomEase.create("hfCe0", "M0,0 C0.539,0 0.312,0.995 1,1");'); + }); +}); + +describe("emitTimelineScript runtime guard", () => { + it("wraps the script in an IIFE that warns when gsap/CustomEase are missing", () => { + const script = emitTimelineScript(motionToGsap(doc)); + expect(script).toContain('typeof gsap === "undefined"'); + expect(script).toContain("console.warn"); + expect(script.startsWith("(function () {")).toBe(true); + expect(script.endsWith("})();")).toBe(true); + }); +}); diff --git a/packages/core/src/figma/emitTimelineScript.ts b/packages/core/src/figma/emitTimelineScript.ts new file mode 100644 index 0000000000..9ec93d4ec1 --- /dev/null +++ b/packages/core/src/figma/emitTimelineScript.ts @@ -0,0 +1,53 @@ +import type { GsapTween, TimelineSpec } from "./types"; + +function lit(value: string): string { + return JSON.stringify(value); +} + +function num(value: number): number { + return Math.round(value * 1e6) / 1e6; +} + +function val(value: number | string): string { + return typeof value === "number" ? String(num(value)) : JSON.stringify(value); +} + +function emitTween(t: GsapTween): string[] { + const set = `tl.set(${lit(t.selector)}, { ${t.property}: ${val(t.initial)} }, 0);`; + const kf = t.steps + .map( + (s) => + `{ ${t.property}: ${val(s.value)}, duration: ${num(s.duration)}, ease: ${lit(s.ease)} }`, + ) + .join(", "); + const repeat = t.repeat > 0 ? `, repeat: ${t.repeat}` : ""; + return [set, `tl.to(${lit(t.selector)}, { keyframes: [${kf}]${repeat} }, 0);`]; +} + +export function emitTimelineScript(spec: TimelineSpec): string { + const lines: string[] = []; + // Guard the whole script: if the composition author forgot the GSAP or + // CustomEase CDN tag, warn loudly instead of throwing mid-script and + // silently never registering the timeline. + lines.push("(function () {"); + const needsCustomEase = spec.customEases.length > 0; + const missing = needsCustomEase + ? 'typeof gsap === "undefined" || typeof CustomEase === "undefined"' + : 'typeof gsap === "undefined"'; + const libs = needsCustomEase ? "gsap + CustomEase" : "gsap"; + lines.push( + `if (${missing}) { console.warn(${lit(`figma timeline ${spec.timelineId}: ${libs} not loaded — add the CDN