skills/**/*.test.mjs files (e.g. skills/media-use/scripts/resolve.test.mjs
and skills/media-use/scripts/lib/manifest.test.mjs) are bare `node --test`
files with only `node:` built-in imports. They aren't part of any workspace
package, and the existing `Test` job's path filter (the `code` filter in
the `changes` job) excludes `skills/**`, so even on PRs that touch only
skills/ those tests never run.
This matters for regression guards. The shell-injection probe test added in
HF#1723 feeds probe() a filename containing `clip"; touch INJECTED; echo
".mp4` and asserts no marker file is created. The test passes locally but
under the current job graph it would never run in CI on a follow-up skills/
change that re-introduces the bug.
Closing the gap with a dedicated `Test: skills` job rather than relaxing
the `code` filter. The existing `Test` job's steps run `bun run test:scripts`
(hardcoded file list) and `bun run --filter '*' test` (workspace packages
only), neither of which would actually execute skills tests even if the
filter let `skills/**` through. The dedicated job needs no `bun install`,
just node 22, since the tests only import from `node:` and relative paths.
The discovery step shells out to `find` and fails loudly when zero test
files match, so a future rename or layout change can't silently turn this
into a no-op pass.
Spotted by Via in HF#1723 review thread, confirmed by James as a separate
follow-up rather than a blocker for HF#1723.
--
Jerrai (https://claude.com/claude-code)
* feat(slideshow): per-slide autoplay (manual-advance, opt-in)
Adds an opt-in `autoplay` flag to slideshow slides: when the presenter lands
on a video slide, its `<video>` plays from the start. The slideshow still
holds and never auto-advances — the presenter clicks Next when ready. This
covers compositions whose own controls can't be clicked (the player renders
the composition pointer-events:none).
Plumbing (done, tested):
- core: `SlideRef.autoplay?: boolean`, parsed + validated in parseSlideshow
(a non-boolean autoplay rejects the manifest); carried through resolve.
- controller: optional `PlayerPort.playSceneMedia(sceneId)`, fired only on
forward `enterSlide` for autoplay slides (not resume/back/sync, so the
audience — which mirrors the presenter's media events — isn't double-driven).
- component: `playSceneDocumentMedia` reaches the same-origin composition
iframe, finds the scene's `<video>`, and asserts playback; `stopMedia`
(already wired on slide change) resets it. An autoplay token cancels a
pending start when the slide changes.
- tests: controller autoplay behavior + parser flag round-trip/validation
(131 player + 22 core slideshow tests pass).
KNOWN LIMITATION — runtime media-start needs the player media model (@vance):
On current main the clip<->timeline binding from #1601 keeps every clip synced
and *paused* to the held timeline frame, which wins against playSceneMedia's
play() — so the clip does not actually start on main yet (it does on the
pre-#1601 player). The correct fix is a sanctioned "let this clip free-run
while the timeline holds" path in the player/runtime media controller. Flagging
for Vance to wire the start into the #1601 media model (or rebase onto it) when
back. The plumbing above is the stable surface that hook plugs into.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(slideshow): address code-review on per-slide autoplay
- guard playSceneDocumentMedia behind resolveMode() !== "audience": the
audience mirrors the presenter's media events, so it must not independently
drive its own copy of the clip.
- drop the per-enter window pointerdown/keydown "gesture retry" listeners,
which leaked when muted autoplay succeeded without a gesture. The poll already
re-asserts play(), so a gesture within the window is picked up next tick.
- stop polling once the clip is advancing across two ticks (was re-asserting
play() for the full window even after playback was confirmed).
- cancel any in-flight autoplay loop on disconnectedCallback (bump the token).
- split the poll into findSceneVideo + stepAutoplay helpers (keeps each small).
- fix the enterSlide comment: autoplay fires from enterSlide (next/prev/
goToSlide), not resumeSlide (back/backToMain/syncTo).
- parser: isOptionalBoolean type guard instead of a one-off helper; drop `as`
assertions in the new controller test.
131 player + 22 core slideshow tests pass; lint/format/typecheck/fallow clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(slideshow): autoplay skill guidance + address review nits
Addresses review feedback on #1708:
- skill: document per-slide `autoplay` in the slideshow standalone-harness
reference — when to use it (video is the slide's primary content, its end is
the advance cue) vs not (background/ambient loops, footage talked over), per
Vance's guidance, before merge.
- play() rejection is no longer blanket-swallowed: AbortError (timeline-sync
seek interrupt) and NotAllowedError (gesture-gated autoplay) are expected and
ignored; any other rejection is surfaced once via console.warn (Via nit 1).
- clarify in the SlideRef.autoplay doc that it plays the scene's FIRST <video>
(Via nit 2).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Swap execSync(<shell-string>) → execFileSync(file, [argv]) in probe.mjs, heygen-search.mjs, and eval.mjs so hostile filenames / queries / manifest metadata can't inject shell. Adds probe.test.mjs regression guard and a CI Test (skills) job so it actually runs. Closes the media-use High/Critical scanner alert.
* docs: list all 19 skills in README + add CLAUDE.md maintenance reminder
Agents discover skills via the README, so silently-out-of-date entries
kill discovery. This change:
- Adds a `## Skills` section to the README listing all 19 skills,
grouped Router / Creation workflows / Domain skills, with a one-line
"use when" blurb for each (sourced from each skill's SKILL.md
frontmatter `description:`).
- Updates the existing CLAUDE.md `## Skills` section to cover all 19
skills (was missing the domain skills, `/media-use`, `/slideshow`,
and `/music-to-video`), mirroring the README's Router / Creation /
Domain grouping.
- Adds a "Skill catalog maintenance" section to CLAUDE.md so future
skill additions / renames update both surfaces and the
`/hyperframes` router skill in lockstep.
Docs-only — no source or test changes.
— Jerrai (https://claude.com/claude-code)
* docs(mintlify): add skills catalog page + extend maintenance reminder
Per follow-up on HF#1722: the Mintlify docs at
hyperframes.heygen.com also need the skills catalog so agent
discoverability is consistent across README and docs site.
- New: docs/guides/skills.mdx (3-group catalog — router / creation
workflows / domain skills — mirrors README structure, sourced from
the same SKILL.md frontmatter)
- Update: docs/quickstart.mdx — completes the workflow-skills list
(was missing /music-to-video, /slideshow, /general-video) and
cross-links the new page
- Update: docs/introduction.mdx — adds a skills-catalog card to the
hero CardGroup and the Next Steps section
- Update: docs/docs.json — adds /guides/skills to the Guides nav
- Update: CLAUDE.md "Skill catalog maintenance" — adds
docs/guides/skills.mdx as the third sync target alongside README
and skills/hyperframes/SKILL.md, and notes the count drift surface
(README + CLAUDE.md mention "19 AI agent skills" in their intros;
the new docs page deliberately omits a count to avoid drift)
Docs-only — no source, packages, or test changes.
— Jerrai (https://claude.com/claude-code)
* docs(readme): oxfmt table column-alignment fix
Pure whitespace — oxfmt's table-column alignment caught README.md
after the previous commit. No content change.
— Jerrai (https://claude.com/claude-code)
* docs(skills): reconcile install-command contract across README/CLAUDE/Mintlify
Per Magi's review on HF#1722: the new README/CLAUDE/skills.mdx pages
described bare `npx skills add heygen-com/hyperframes` as installing all
19 skills, while existing quickstart/prompting docs said the bare command
opens a picker and `--all` installs everything.
Verified actual CLI behavior with `npx skills add --help` and a clean-dir
run: bare command opens an interactive picker for human users (the CLI
help documents `--all` as "Shorthand for --skill '*' --agent '*' -y" —
the picker-skipping form). Inside an agent the bare command auto-installs
all non-interactively, but that's an agent-detection UX shortcut, not the
public contract — documenting the picker is correct for human readers.
All touched docs now use the consistent contract:
- `npx skills add heygen-com/hyperframes` -> interactive picker
- `npx skills add heygen-com/hyperframes --all` -> install all 19 (skips picker)
- `npx skills add heygen-com/hyperframes --skill <name>` -> install just one
Files updated: README.md, CLAUDE.md, docs/guides/skills.mdx. Existing
docs/quickstart.mdx and docs/guides/prompting.mdx already used this
contract and are unchanged.
— Jerrai (https://claude.com/claude-code)
* feat(slideshow): auto-set interactive on inner player
The slideshow now sets the `interactive` attribute on its inner
<hyperframes-player> instances at mount time, so pointer events
reach the composition iframe automatically. Removes the
agent-compliance burden of having to remember to add `interactive`
on every player tag inside a slideshow.
Idempotent: an author-supplied `interactive` attribute (any value,
including `interactive="false"`) is preserved. A MutationObserver
also picks up players inserted dynamically after the initial mount.
Standalone player usage outside a slideshow still requires the
explicit attribute — that surface is unchanged.
Skill guidance at skills/slideshow/SKILL.md updated to reflect the
automatic behavior.
* docs(slideshow): clarify interactive attribute semantics
Per Rames R1 review feedback: the test comment implied
`interactive="false"` is an author opt-out, but `:host([interactive])`
is presence-matching per HTML boolean-attribute convention — so any
value (including "false") enables pointer events at runtime. The
slideshow's mechanical wire-up preserves any author-supplied value
verbatim for DOM hygiene, not as a runtime opt-out.
Speed-curve editor: a fixed-square cubic-bezier graph (grid, linear reference,
draggable handles, live preview) for editing eases; conventional preset grid.
Class/selector tweens: attribute `gsap.from(".dot", …)`-style tweens to every
matching element so they surface in the inspector and keep their timeline
keyframe diamonds when the clip is selected.
Apply-to-all easing: a "Set all…" control sets easeEach and strips every
per-keyframe ease override in one mutation (AE select-all + F9). Implemented in
BOTH gsap writers — the acorn writer and the recast writer (the default server
path); the recast side was missing resetKeyframeEases, so "Set all" set easeEach
but left per-keyframe eases in place.
Per-keyframe size: resizing an animated element writes a width/height keyframe
at the playhead — other keyframes keep their size — instead of a global
gsap.set hold; static elements keep the simple global resize. The extra size
tween exposed a motion-path bug (the overlay read whichever tween contained the
playhead), fixed with an opt-in requireChannels filter so the path only reads
the positional tween.
Inferred Timing: derive Start/End/Duration from an element's animations when it
has no authored clip range, instead of showing 0.00s.
Ease labels now surface the raw GSAP token (power2.out, back.out, …) instead of
invented names ("Smooth slowdown") that confused authors.
Also pass the preview iframe to the inspector's animation hook so element
resolution runs, and remove the unused editDebugLog facility.
The `media-use` SKILL.md `description:` was an unquoted YAML scalar containing
a mid-value `: ` (`...the full cascade: project cache...`). YAML 1.2 reads
that as a nested mapping and the parse fails with "Nested mappings are not
allowed in compact mappings". `skills add` aborts the entire install when any
one skill fails to parse, so this single file blocked installing all 19
skills for everyone following the README's `npx skills add heygen-com/hyperframes`.
- Replace the offending `: ` with ` — ` (keeps the plain-scalar style used by
the other 18 skills; the description already uses `—` as a separator).
- Add a frontmatter guard to scripts/lint-skills.ts that flags unquoted
top-level scalars containing `: ` — the exact ambiguity — so this can't
regress. No new dependency.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(media-use): core infrastructure — manifest, cache, adopt, probe
Foundation for media-use — the media resolution layer for HyperFrames.
- manifest.mjs: JSONL read/write/find for .media/manifest.jsonl
- index-gen.mjs: regenerate agent-readable index.md from manifest
- cache.mjs: content-addressed global cache at ~/.media/ (SHA-256, sentinel)
- freeze.mjs: download URL or copy local file to .media/
- probe.mjs: extract duration/dimensions via ffprobe
- adopt.mjs: scan assets/ directory, register existing files with metadata
- 19 passing tests (manifest round-trip, cache, promote, index generation)
* fix(media-use): oxfmt formatting + cap freeze download size
Format adopt/cache/probe/manifest.test (CI oxfmt --check gate).
Cap freezeUrl downloads at 256MB so a hostile/runaway URL can't fill
the disk (addresses CodeQL #670: network data written to file).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(media-use): resolve engine + all providers + brand from frame.md
- resolve.mjs: cheapest-first cascade
- BGM/SFX via heygen --headers, Image/Icon via heygen asset search
- Brand tokens from frame.md / design.md (local, no API)
- SKILL.md: full agent docs + hyperframes.dev/design redirect
- Router skill + workflow skill references
* fix(media-use): oxfmt formatting for resolve + providers
Format brand/heygen-search/providers/sfx providers + resolve.mjs
(CI oxfmt --check gate).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(media-use): align providers with the real heygen CLI surface (v0.1.6)
Verified live against the official Go `heygen` CLI v0.1.6 with a valid key:
- Caller attribution: pass `--headers 'X-HeyGen-Client-Source: media-use'`
(the allowlisted flag the CLI added for media-use in v0.1.6). The old
`--x-source media-use` was never a real flag and broke every call.
- Command is `asset search` (the `list` leaf was dropped in v0.1.6), not
`asset search list`.
- `--min-score` is sent server-side: honored by `audio sounds list`, but the
`asset search` backend rejects it and returns no score field, so only the
audio providers pass it (image/icon don't).
- Drop hardcoded `ext` so resolve.mjs derives it from the URL: catalog icons
are .png (not .svg), some BGM is .wav (not .mp3).
Also: surface CLI/auth failures on stderr instead of swallowing them as
'no results', carry icon width/height through, and document the heygen CLI
install + >= v0.1.6 requirement.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(studio): redesign Asset tab + fix beat analysis auto-trigger
Asset tab: categorized sections, filter chips, text search, audio
spectrum visualizer, "in use" badge, manifest metadata, panel tokens.
Beat fix: only run analysis when a beats file exists on disk.
* fix(studio): oxfmt formatting for AssetsTab
CI oxfmt --check gate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(media-use): resolve tests + eval harness
12 resolve engine tests + eval against 7 real registry blocks.
* fix(media-use): oxfmt formatting for eval + resolve tests
CI oxfmt --check gate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(media-use): core infrastructure — manifest, cache, adopt, probe
Foundation for media-use — the media resolution layer for HyperFrames.
- manifest.mjs: JSONL read/write/find for .media/manifest.jsonl
- index-gen.mjs: regenerate agent-readable index.md from manifest
- cache.mjs: content-addressed global cache at ~/.media/ (SHA-256, sentinel)
- freeze.mjs: download URL or copy local file to .media/
- probe.mjs: extract duration/dimensions via ffprobe
- adopt.mjs: scan assets/ directory, register existing files with metadata
- 19 passing tests (manifest round-trip, cache, promote, index generation)
* fix(media-use): oxfmt formatting + cap freeze download size
Format adopt/cache/probe/manifest.test (CI oxfmt --check gate).
Cap freezeUrl downloads at 256MB so a hostile/runaway URL can't fill
the disk (addresses CodeQL #670: network data written to file).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(media-use): resolve engine + all providers + brand from frame.md
- resolve.mjs: cheapest-first cascade
- BGM/SFX via heygen --headers, Image/Icon via heygen asset search
- Brand tokens from frame.md / design.md (local, no API)
- SKILL.md: full agent docs + hyperframes.dev/design redirect
- Router skill + workflow skill references
* fix(media-use): oxfmt formatting for resolve + providers
Format brand/heygen-search/providers/sfx providers + resolve.mjs
(CI oxfmt --check gate).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(media-use): align providers with the real heygen CLI surface (v0.1.6)
Verified live against the official Go `heygen` CLI v0.1.6 with a valid key:
- Caller attribution: pass `--headers 'X-HeyGen-Client-Source: media-use'`
(the allowlisted flag the CLI added for media-use in v0.1.6). The old
`--x-source media-use` was never a real flag and broke every call.
- Command is `asset search` (the `list` leaf was dropped in v0.1.6), not
`asset search list`.
- `--min-score` is sent server-side: honored by `audio sounds list`, but the
`asset search` backend rejects it and returns no score field, so only the
audio providers pass it (image/icon don't).
- Drop hardcoded `ext` so resolve.mjs derives it from the URL: catalog icons
are .png (not .svg), some BGM is .wav (not .mp3).
Also: surface CLI/auth failures on stderr instead of swallowing them as
'no results', carry icon width/height through, and document the heygen CLI
install + >= v0.1.6 requirement.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(studio): redesign Asset tab + fix beat analysis auto-trigger
Asset tab: categorized sections, filter chips, text search, audio
spectrum visualizer, "in use" badge, manifest metadata, panel tokens.
Beat fix: only run analysis when a beats file exists on disk.
* fix(studio): oxfmt formatting for AssetsTab
CI oxfmt --check gate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(media-use): core infrastructure — manifest, cache, adopt, probe
Foundation for media-use — the media resolution layer for HyperFrames.
- manifest.mjs: JSONL read/write/find for .media/manifest.jsonl
- index-gen.mjs: regenerate agent-readable index.md from manifest
- cache.mjs: content-addressed global cache at ~/.media/ (SHA-256, sentinel)
- freeze.mjs: download URL or copy local file to .media/
- probe.mjs: extract duration/dimensions via ffprobe
- adopt.mjs: scan assets/ directory, register existing files with metadata
- 19 passing tests (manifest round-trip, cache, promote, index generation)
* fix(media-use): oxfmt formatting + cap freeze download size
Format adopt/cache/probe/manifest.test (CI oxfmt --check gate).
Cap freezeUrl downloads at 256MB so a hostile/runaway URL can't fill
the disk (addresses CodeQL #670: network data written to file).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(media-use): resolve engine + all providers + brand from frame.md
- resolve.mjs: cheapest-first cascade
- BGM/SFX via heygen --headers, Image/Icon via heygen asset search
- Brand tokens from frame.md / design.md (local, no API)
- SKILL.md: full agent docs + hyperframes.dev/design redirect
- Router skill + workflow skill references
* fix(media-use): oxfmt formatting for resolve + providers
Format brand/heygen-search/providers/sfx providers + resolve.mjs
(CI oxfmt --check gate).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(media-use): align providers with the real heygen CLI surface (v0.1.6)
Verified live against the official Go `heygen` CLI v0.1.6 with a valid key:
- Caller attribution: pass `--headers 'X-HeyGen-Client-Source: media-use'`
(the allowlisted flag the CLI added for media-use in v0.1.6). The old
`--x-source media-use` was never a real flag and broke every call.
- Command is `asset search` (the `list` leaf was dropped in v0.1.6), not
`asset search list`.
- `--min-score` is sent server-side: honored by `audio sounds list`, but the
`asset search` backend rejects it and returns no score field, so only the
audio providers pass it (image/icon don't).
- Drop hardcoded `ext` so resolve.mjs derives it from the URL: catalog icons
are .png (not .svg), some BGM is .wav (not .mp3).
Also: surface CLI/auth failures on stderr instead of swallowing them as
'no results', carry icon width/height through, and document the heygen CLI
install + >= v0.1.6 requirement.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(media-use): core infrastructure — manifest, cache, adopt, probe
Foundation for media-use — the media resolution layer for HyperFrames.
- manifest.mjs: JSONL read/write/find for .media/manifest.jsonl
- index-gen.mjs: regenerate agent-readable index.md from manifest
- cache.mjs: content-addressed global cache at ~/.media/ (SHA-256, sentinel)
- freeze.mjs: download URL or copy local file to .media/
- probe.mjs: extract duration/dimensions via ffprobe
- adopt.mjs: scan assets/ directory, register existing files with metadata
- 19 passing tests (manifest round-trip, cache, promote, index generation)
* fix(media-use): oxfmt formatting + cap freeze download size
Format adopt/cache/probe/manifest.test (CI oxfmt --check gate).
Cap freezeUrl downloads at 256MB so a hostile/runaway URL can't fill
the disk (addresses CodeQL #670: network data written to file).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Studio/preview stamps `data-start` onto ID'd and GSAP-targeted flow
children (eg. a <header>/<footer> in a flex column) so the design panel
can discover them. applyClipLayout then force-absolutized those stamped
elements as if they were authored overlay clips, collapsing the layout:
the footer shrink-wrapped and its `justify-content: space-between`
clustered into the top-left, while the rendered video — which never
stamps (production renders run as the top-level page, not in an iframe) —
stayed correct.
Mark runtime-stamped clips with `data-hf-autostamped` and skip them in
applyClipLayout so they remain in document flow. The preview now matches
the rendered video (true WYSIWYG). Authored overlay clips are unchanged,
so the golden regression suite is unaffected.
`present` and `play` render compositions in the standalone browser player,
resolving the player/slideshow IIFE bundles via resolvePlayerPath /
resolveSlideshowPath. Those resolvers look for the bundles alongside the built
CLI (dist/hyperframes-player.global.js, dist/hyperframes-slideshow.global.js),
but build-copy.mjs never staged them into dist/. The remaining candidate paths
are monorepo-dev only, so an npm install has nothing to resolve.
Result: `npx hyperframes present` always failed with
"@hyperframes/player not found", forcing users to run the presenter from a
monorepo checkout.
Copy both player globals from packages/player/dist into the CLI dist during
build:copy (existsSync-guarded + warn, matching the surrounding pattern). The
runtime bundle is already handled by build:runtime. Verified: the globals now
appear in `npm pack`, and `node dist/cli.js present` starts without the
player-not-found error.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add formatSrt/formatVtt/wordsToCues to normalize.ts (the inverse of the
existing parseSrt/parseVtt) and a 'hyperframes transcribe <transcript> --to
srt|vtt' export mode. Word-level whisper transcripts group into cues on
sentence boundaries with maxChars/maxGap guards; imported phrase-level cues
pass through unchanged. Default transcribe behavior is unchanged and no new
dependencies are added.
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
* chore(studio): remove all console.* calls from studio package
* chore(studio): address review — remove dead stubs, restore consent notice
- Delete empty if-blocks left after console removal (snapTargetCollection,
Player asset-poll, useTimelineSyncCallbacks 5s probe, useGestureRecording
dev guard + now-unused isDevBuild) and the stale "surface in dev" comment.
- Drop the dangling no-console pragma + dead duplicate-id branch in sourcePatcher.
- Restore the one-time telemetry consent disclosure in showNoticeOnce (kept
behind a pragma — it is a user-facing notice, not debug noise).
- Remove the missed timelineIcons console.warn while preserving the
`tag || "div"` null-safety fallback.
- Route caption auto-save failures (a data-loss path) through telemetry
instead of swallowing silently.
- Restore the accidentally-clobbered css-var-fonts output.mp4 fixture.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(runtime): immediateRender for set tweens + array timeline normalization
- Set tweens now emit immediateRender:true so they render on page load
without requiring the runtime to seek past position 0
- Runtime IIFE normalizes array timelines (window.__timelines = [tl])
to keyed objects, and auto-adds data-start on root elements
- Drag teardown clears translate:none to prevent #1673 fly-off
- Position-only set tweens hidden from timeline diamonds (3 cache paths)
- Parser: ease-only keyframe update preserves existing properties
* fix(runtime): address review — restore perf gate, debug surface, scrub restore
- Restore the #1651 skipForInjectedVideo gate in media.ts that was dropped on
restack — avoids ~2400 wasted per-tick seeks on video-heavy renders.
- Restore the console.debug body + docstring bullet of swallow() in
diagnostics.ts: the __hfDebug opt-in debug surface had been gutted to an
empty if-block.
- Rebind: after the progress-cycle set() kick, seek to state.currentTime via
totalTime() instead of snapping to 0, so a rebind after scrub / soft-reload
restore keeps the playhead.
- Array __timelines normalization + data-start default now resolve the root
via a shared findRootCompositionEl() that honors data-root="true" first
(matches resolveRootCompositionElement, which now delegates to it).
- Ease-only keyframe update leaves a primitive (non-object) keyframe value
untouched instead of wiping it to {}; add a preservation unit test.
- Document the boundDuration<=0 progress(1) kick + restore the STATIC-case
comment in gsapRuntimeBridge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(studio): marquee multi-selection + off-canvas indicators
- Click+drag on empty canvas draws dashed selection rectangle
- SAT/OBB intersection handles rotated/scaled/skewed elements
- Shift+marquee adds to existing selection
- Click on empty canvas deselects
- Off-canvas elements show dashed outline indicators (clickable)
- Dashed border only shows outside canvas, solid inside (clip-path)
- 12 geometry unit tests
* feat(studio): address review — group-aware off-canvas indicators + fixes
- Off-canvas indicator suppression now skips every selected element (primary
AND marquee group members), not just the primary, so group members no longer
render a doubled overlay (group rect + dashed indicator).
- Drop selection from the off-canvas layout effect deps; the selected-element
filter runs at render time. Avoids re-walking geometry on each selection change.
- applyMarqueeSelection now honors STUDIO_INSPECTOR_PANELS_ENABLED.
- Restore the stale-selection clear in useDomEditPreviewSync when the selected
element no longer resolves after a re-sync. Drag-release stays handled by
suppressNextBoxClickRef.
- Off-canvas indicator is keyboard-accessible; canvas cursor driven by marquee
rect state, not a render-time ref read.
- Rename partiallyOutside -> extendsOutsideComp + comment the clip-path hit-test.
- Extract OffCanvasIndicators into its own component (DomEditOverlay was already
over the 600-LOC cap on this branch; extraction brings it under).
- Declare onUpdateKeyframeEase on PropertyPanelProps so this branch typechecks
standalone (handler + wiring already here; only the type had leaked upstack).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(studio): remove all console.* calls from studio package
* chore(studio): address review — remove dead stubs, restore consent notice
- Delete empty if-blocks left after console removal (snapTargetCollection,
Player asset-poll, useTimelineSyncCallbacks 5s probe, useGestureRecording
dev guard + now-unused isDevBuild) and the stale "surface in dev" comment.
- Drop the dangling no-console pragma + dead duplicate-id branch in sourcePatcher.
- Restore the one-time telemetry consent disclosure in showNoticeOnce (kept
behind a pragma — it is a user-facing notice, not debug noise).
- Remove the missed timelineIcons console.warn while preserving the
`tag || "div"` null-safety fallback.
- Route caption auto-save failures (a data-loss path) through telemetry
instead of swallowing silently.
- Restore the accidentally-clobbered css-var-fonts output.mp4 fixture.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(runtime): immediateRender for set tweens + array timeline normalization
- Set tweens now emit immediateRender:true so they render on page load
without requiring the runtime to seek past position 0
- Runtime IIFE normalizes array timelines (window.__timelines = [tl])
to keyed objects, and auto-adds data-start on root elements
- Drag teardown clears translate:none to prevent #1673 fly-off
- Position-only set tweens hidden from timeline diamonds (3 cache paths)
- Parser: ease-only keyframe update preserves existing properties
* fix(runtime): address review — restore perf gate, debug surface, scrub restore
- Restore the #1651 skipForInjectedVideo gate in media.ts that was dropped on
restack — avoids ~2400 wasted per-tick seeks on video-heavy renders.
- Restore the console.debug body + docstring bullet of swallow() in
diagnostics.ts: the __hfDebug opt-in debug surface had been gutted to an
empty if-block.
- Rebind: after the progress-cycle set() kick, seek to state.currentTime via
totalTime() instead of snapping to 0, so a rebind after scrub / soft-reload
restore keeps the playhead.
- Array __timelines normalization + data-start default now resolve the root
via a shared findRootCompositionEl() that honors data-root="true" first
(matches resolveRootCompositionElement, which now delegates to it).
- Ease-only keyframe update leaves a primitive (non-object) keyframe value
untouched instead of wiping it to {}; add a preservation unit test.
- Document the boundDuration<=0 progress(1) kick + restore the STATIC-case
comment in gsapRuntimeBridge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(studio): remove all console.* calls from studio package
* chore(studio): address review — remove dead stubs, restore consent notice
- Delete empty if-blocks left after console removal (snapTargetCollection,
Player asset-poll, useTimelineSyncCallbacks 5s probe, useGestureRecording
dev guard + now-unused isDevBuild) and the stale "surface in dev" comment.
- Drop the dangling no-console pragma + dead duplicate-id branch in sourcePatcher.
- Restore the one-time telemetry consent disclosure in showNoticeOnce (kept
behind a pragma — it is a user-facing notice, not debug noise).
- Remove the missed timelineIcons console.warn while preserving the
`tag || "div"` null-safety fallback.
- Route caption auto-save failures (a data-loss path) through telemetry
instead of swallowing silently.
- Restore the accidentally-clobbered css-var-fonts output.mp4 fixture.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(hyperframes): route /slideshow as a workflow, not a capability
Move /slideshow out of the domain-skill capability map and into the
intent router as a top-level workflow. It is an intent-gated orchestration
that produces a navigable deck, not an atomic capability loaded on demand.
Also clarify that workflows need not output a video: /slideshow builds a
deck and /remotion-to-hyperframes ports a composition. Adds the cheat-sheet
row, a disambiguation bullet, and the per-workflow detail block.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(hyperframes): sharpen /slideshow disambiguation to match its intent gate
Reword the slideshow disambiguation bullet to match the skill's actual
intent-confirmation behavior: an explicit "slideshow" request proceeds
directly; an adjacent trigger ("deck / slides / presentation / convert this
page") makes /slideshow confirm before authoring and switch to the
appropriate non-slideshow workflow if not. Drops the inaccurate "may
actually want a video" narrowing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: handle caption skin workflow
* docs(skills): simplify the finalize step across video workflows
- Drop --strict-layout; all skills use plain `hyperframes inspect`
- Add the caption text_box_overflow false-positive note to faceless-explainer
- On a failed check, the orchestrator makes the cheapest safe edit itself
(no worker re-dispatch / Step 3 backtrack language)
- Snapshot: glance at the stitched contact-sheet.jpg and move on
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(auth): onboarding-first `auth status` + shared TTS/BGM preflight
When no HeyGen credential is configured, `hyperframes auth status` now
prints registration-first guidance instead of a terse error:
- Interactive / agent-driven sessions get sign-in guidance led by
`hyperframes auth login` (the OAuth step that also creates an account
and is shared with heygen-cli), and never steer users to a per-repo
`.env`. CI / non-interactive runs get a terse note. Exit 1 is kept so
the "am I logged in?" `$?` contract still holds.
- It probes which local engine voice/music will fall back to (Kokoro /
MusicGen, mirroring the skill resolution order) and whether their
Python deps are installed, with a pip hint when missing. `--json`
exposes `recommended_action` + `offline_engines` for skills to branch.
- `doctor` gains matching "TTS (Kokoro)" / "BGM (MusicGen)" checks via
the same shared probe (findPython/hasPythonModules extracted to
tts/python.ts; provider resolution in audio/providers.ts).
Every TTS/BGM workflow now relays this at Step 0 (setup) instead of
improvising its own "missing key" prompt: pr-to-video, product-launch-
video, faceless-explainer, website-to-video, music-to-video. The
canonical behavior + key-priority table live once in hyperframes-media.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pr-to-video): scale recommended video length to PR change size
Step 0 led with a fixed ~60-90s length default. Now the recommended
length is derived from the PR's diff stat (lines added+deleted, nudged
by file count) on a tier scale (trivial ~20-40s → large ~110-180s, hard
cap ~3 min), reusing the same PR peek already done to infer the angle.
The agent states the basis when proposing it, and a huge PR with one
headline change still stays tight. User can always override.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(captions): embed brand fonts whose files use separators
brandFontFaces() matched font files by stripping only whitespace, so an
underscore/hyphen-named file (TT_Norms_Pro_Bold.woff2) never matched the
family key "ttnormspro" — captions shipped with no @font-face, the
font_family_without_font_face bug. Now both family and filename normalize
away all non-alphanumerics; families match longest-key-first so a parent
family can't swallow a more specific one's files (TT Norms Pro vs Mono);
each file is claimed once; "demibold" ranks before "bold"; and when
nothing matches it warns loudly at build time instead of returning "".
Also: parseFonts() falls back to h1/h2/title/hero display roles, and the
frame-worker + caption authoring docs spell out that only shipped font
files render — no system CJK/Devanagari families on the headless renderer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(hyperframes-media): enforce sign-in preflight on standalone BGM/TTS
A one-off "generate me a BGM" request went straight to local MusicGen
without recommending sign-in: bgm.md/tts.md framed the no-credential path
as an automatic fallback, so the generation path bypassed the Preflight
stop, and the preflight used a bare `hyperframes auth status` that isn't
on PATH in a fresh `npx skills` project.
- Preflight now applies to one-off generation as well as workflows, uses
`npx hyperframes auth status`, and says: if the CLI can't run, still
recommend signing in and STOP — never treat "no credential" as a silent
green light for local generation.
- bgm.md and tts.md point at the Preflight before generating, reframing
local generation as the fallback the user opts into, not a default.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(auth): add Authentication & API keys guide
Document signing in, the keys each capability (voice, music, capture)
uses, their resolution priority, and the fully local fallback. Add the
guide to the nav and cross-link it from the cloud deploy note and the
CLI env-var reference.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(lint): strip HTML comments in a fixpoint loop (CodeQL)
Single-pass <!-- --> removal can re-form a complete comment from
adjacent markers (e.g. `<<!-- -->!-- ... -->`), letting a decoy
<template> survive and hijack the template-boundary match. Loop to a
fixpoint, mirroring the captions.mjs precedent; add a regression test
that fails on single-pass (2 root findings) and passes on the loop.
Also wrap the build-frame.mjs node:fs imports to satisfy oxfmt — the
new copyFileSync import pushed the line past the width limit, which
was the sole cause of the Format / Preflight CI failures.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(lint): strip HTML comments with a linear scan (CodeQL ReDoS)
The fixpoint loop still ran a /<!--[\s\S]*?-->/ regex per pass, which
backtracks O(n^2) on inputs with many unterminated "<!--" — CodeQL
js/polynomial-redos (high). Looping the same regex (the prescribed
fix) never addressed this; only the regex itself does.
Replace it with an indexOf-based linear strip in utils.ts
(stripHtmlComments), kept in a fixpoint loop so markers that re-form
when a comment is removed are still stripped. 200k unterminated
"<!--" now strips in ~3ms instead of quadratic time; behavior is
otherwise unchanged — unterminated comments are kept verbatim, as the
old regex left them. The re-forming regression test still guards it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(auth): make TTS/BGM sign-in guidance accurate and runnable
From team review of the not-signed-in onboarding:
- OAuth is a `hyperframes auth login` feature only. The separate `heygen`
CLI is API-key-only — `heygen auth login` stores a pasted key, it is not
OAuth and does not create an account. Stop presenting the two CLIs as the
same OAuth/sign-up step.
- Use `npx hyperframes` in every imperative and runtime hint. Bare
`hyperframes` is not on PATH on a fresh machine (command not found); only
`npx hyperframes` is guaranteed. Also updates the JSON recommended_action.
- Drop `heygen auth login` from the terminal/skill onboarding: it needs its
own install and there is no `npx heygen`, so it was a command-not-found
trap. The shared-credential fact stays in the reference docs.
Covers the `auth status` guidance + tests, the Authentication docs, the
shared hyperframes-media preflight (SKILL, requirements, tts, error hints),
and the `npx hyperframes auth status` preflight in every TTS/BGM workflow
(pr-to-video, product-launch-video, faceless-explainer, website-to-video,
music-to-video).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## What
Studio preview now resolves `<video src="../../assets/x.mp4">` (and the
same shape for `<img>`, `<audio>`, inline `style` `url()`, and `<style>`
CSS `url()`) against the sub-composition's URL — matching what the
server-side bundler already does for the render path.
## Why
Authored compositions live at `compositions/frames/*.html` and reference
project-root assets either as plain `assets/x.mp4` (already correct
because the main document's `<base href>` points at the project preview
root) or as `../../assets/x.mp4` (the explicit project-root-relative
form). The server-side `inlineSubCompositions` flattens sub-comps into
`index.html` and rewrites the `../`-form against the sub-comp's source
path so it resolves against the project root in the baked render.
The browser-side runtime that mounts external sub-compositions via
`fetch` did no such rewriting. So `<video src="../../assets/x.mp4">`
authored inside a `compositions/frames/scene.html` resolved against the
main document's base href, climbed above the project root, and 404'd in
Studio preview — even though the same path rendered correctly in the
final video. An OSS user (Miao Yang) hit this in a real project.
## How
Added `rewriteSubCompositionAssetPaths` to the runtime
`compositionLoader`. After parsing the fetched sub-composition HTML and
before extracting any nodes, walk the parsed document and rewrite the
same surface the server-side path touches:
- `[src]` and `[href]` attributes on every element
- `[style]` attribute `url(...)` references
- `<style>` element CSS `url(...)` references
The rewrite mirrors the producer's semantics exactly: only values that
start with `../` (or are literal `..`) are rewritten — against the
sub-composition's URL via `new URL(value, compositionUrl)`. Absolute
URLs, root-relative paths, `data:`, hash refs, and plain
`assets/x.mp4` are left untouched. **Plain relative paths must not be
rewritten** because the main document's `<base href>` already covers
them; rewriting would double-prefix the URL.
The walk recurses into `<template>` content because authored
compositions typically wrap their rendered body in a `<template>` and
`querySelectorAll` does not enter template content (it lives in a
detached `DocumentFragment`).
## Test plan
- [x] Unit tests added (6 new tests in
`compositionLoader.test.ts`): rewrites `../`-traversing src on
template-wrapped sub-comps; leaves plain relative paths untouched (no
double-prefix); leaves absolute / data / hash / root-relative URLs
untouched; rewrites CSS `url()` in `<style>` blocks and inline
`style` attributes; rewrites for non-template (full-HTML-doc)
sub-comps.
- [x] Full core test suite green (2065 tests).
- [x] Full studio test suite green (1148 tests).
- [x] Manual verification with the reporter's actual project:
before the fix one `<video>` with a `../../assets/...` src returned
`MEDIA_ELEMENT_ERROR: Format error`; after the fix all 7 `<video>`
elements load (`readyState=4`, correct `currentSrc`). The 6 plain
`assets/...` paths are *unchanged* (no double-prefix) and continue
to resolve via `<base href>` as before.
- [x] `bun run lint`, `bun run format:check`, `bun run typecheck`,
`fallow audit` all green.
Reported by Miao Yang.
— Jerrai (https://claude.com/claude-code)
* feat(telemetry): attribute renders to the authoring workflow skill
Add an optional `--skill` flag to `hyperframes render` and tag the
`render_complete` / `render_error` events with `authoring_skill`, so render
usage can be broken down per authoring workflow. The value is slug-gated (a
malformed value is ignored) and the existing anonymous / opt-out telemetry
pipeline is otherwise unchanged.
Each end-user workflow that renders now passes `--skill=<name>` on its render
command: embedded-captions, faceless-explainer, graphic-overlays,
motion-graphics, music-to-video, pr-to-video, product-launch-video,
remotion-to-hyperframes, website-to-video.
Not instrumented, by design: general-video renders freeform with no canonical
render command to attach to, and slideshow produces an interactive deck rather
than a rendered video. Both can follow up if per-skill numbers are wanted.
* fix(telemetry): address review — shared slug util, equals-form flag, invalid-value warning
- Extract the SKILL_SLUG regex + a normalizeSkillSlug() helper into
telemetry/skill.ts, shared by the `events` and `render` commands (the regex
was duplicated). `render` adopts normalizeSkillSlug (so it now trims the value,
matching `events`); `events` references the shared SKILL_SLUG. + unit test.
- `render` warns on a non-empty but invalid --skill value (e.g. a camelCase
typo) so attribution isn't silently lost — stderr only, never fails the render.
- embedded-captions render script: `--skill embedded-captions` -> `--skill=embedded-captions`.
On an older CLI that does not declare --skill, the space form leaks the value
as a positional and clobbers the project dir (resolveProject fails); the equals
form is parsed as a self-delimiting flag and safely ignored. Verified via Node
parseArgs(strict:false).
Addresses review feedback on the PR (shared util + .trim drift, version-skew
safety, invalid-value visibility).
---------
Co-authored-by: kiritowoo <295860553+kiritowoo@users.noreply.github.com>
* fix(producer): retry probe stage on transient browser errors (#1687)
The distributed render plan stage crashes when headless Chrome encounters
a transient frame detachment ("Navigating frame was detached") during
browser probe, with no retry logic. The plan tarball is never uploaded,
and all downstream chunk workers fail with S3 404.
Add a retry-with-fresh-session mechanism to the probe stage:
- `isTransientBrowserError()` classifier in the engine identifies 9
known transient Puppeteer/Chrome errors (frame detached, target closed,
session closed, protocol error, page crashed, execution context
destroyed, etc.).
- `runProbeStage()` wraps browser session creation + initialization in a
retry loop (max 2 attempts). On transient error: logs structured
diagnostics (attempt, isTransient, error message, elapsed time), closes
the crashed session cleanly, creates a fresh browser, and retries. Non-
transient errors throw immediately without consuming retry budget.
- 17 unit tests for the error classifier, 3 integration tests for retry
behavior (successful retry, immediate throw on non-transient, exhaust
retry budget on persistent transient).
Closes#1687
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review feedback — widen retry scope, deduplicate patterns
- Move createCaptureSession inside the retry try/catch so browser launch
failures (Failed to launch the browser process, ECONNREFUSED) are also
retried — not just initializeSession errors.
- Deduplicate transient error patterns: remove "Protocol error.*Target
closed" (subsumed by "Target closed") and "Navigation failed because
browser has disconnected" (subsumed by "browser has disconnected").
- Add browser launch failure patterns: "Failed to launch the browser
process" and "ECONNREFUSED".
- Add test for createCaptureSession transient throw (browser launch retry).
- Update test mock comment to document sync requirement with engine
pattern list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the root element lacks an explicit data-duration attribute and
there is no GSAP timeline, getDeclaredDuration now computes
max(data-start + data-duration) across all sub-compositions instead
of returning zero.
getTrackStyle() can receive a falsy tag at runtime (e.g. empty string
from timeline element defaults), causing toLowerCase() and startsWith()
to throw. Default to "div" when tag is falsy.
- Platform-specific install instructions for Linux (apt) and Windows
(releases page / cmake) instead of a generic "see GitHub" fallback
- Export getInstallInstructions so doctor can reuse it
- Add whisper-cpp check to `hyperframes doctor` after the Environment
check — reports path when found, shows install hint when missing
`hyperframes init` only installed AI coding skills on the interactive path
(behind a clack confirm). When an agent drives it non-interactively (no TTY),
it just printed `npx skills add ...` and returned — so skills were never
installed and the agent later hit `Unknown skill: <workflow>`.
- init: both interactive and non-interactive branches now run
`npx skills add` with `cwd` set to the new project dir so skills land
there, not in the caller's working directory. Non-interactive additionally
passes `--yes`; when Claude Code is driving (CLAUDECODE env var), adds
`--agent claude-code` so skills target `.claude/skills/`.
- skills: `runSkillsAdd` accepts `cwd` and `extraArgs` so callers can
control where and how skills are installed.
- templates: CLAUDE.md / AGENTS.md now tell agents to run
`npx skills add heygen-com/hyperframes` to install or update skills.
Co-authored-by: Wenbo Zhu <295860553+kiritowoo@users.noreply.github.com>
* feat(skills): add bgm-to-video skill
Add the music-to-video skill: turns a music/BGM track into a kinetic
typography video. Includes the director/builder/music-reader/finalize
agents, reference contracts, beatgrid analysis script, motion-primitive
library, and starter templates.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(lint): catch CSS↔GSAP transform conflicts in scoped selectors and frame sub-compositions
gsap_css_transform_conflict existed but missed the most common real-world
shape (a label centered with CSS translateX(-50%) plus a GSAP xPercent that
stacks to -100% in the capture path), for three independent reasons:
- selector matching was exact-string, so a scoped/grouped GSAP selector
("#root .label, #root .sub") never matched a CSS class rule (.label)
- the acorn parser only captures timeline-rooted calls (tl.to/tl.set), so a
standalone gsap.set("#root .label", { xPercent: -50 }) was invisible to it
- lintProject read compositions/ non-recursively, so per-frame compositions
in compositions/frames/*.html were never linted at all
Fix: token-decompose grouped/descendant/compound selectors and match by
id/class against CSS transform rules; additionally scan standalone gsap.*
transform calls; and recurse into compositions/ subdirectories so frame
sub-compositions are linted.
Adds unit tests (grouped gsap.set repro, descendant tl.to, negative case) and
an end-to-end lintProject test that writes compositions/frames/04-*.html and
asserts the conflict is reported there.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(skills): add beat-synced montage authoring recipe
* feat(skills): unify bgm-to-video flows into music-to-video
Replace bgm-to-video, bgm-to-video-new, bgm-to-video-refactor, and the
standalone beat-sync/montage skills with a single music-to-video skill.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(skills): register music-to-video in the hyperframes router
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(skills): add music-source brief to music-to-video Step 0
Check for user-supplied audio first; otherwise guide BGM generation
via /hyperframes-media. Note the skill targets fast, high-energy BGM.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(producer): restore css-var-fonts regression baseline
Accidentally deleted by a prior `git add -A`; it is the golden output.mp4
the distributed regression harness diffs against. Restored byte-identical
to main.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* style(skills): apply oxfmt to music-to-video and router docs
Fixes the Format / Preflight CI checks on the new skill files.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(producer): store css-var-fonts baseline as raw binary, not LFS pointer
The previous restore was re-filtered into a 130-byte LFS pointer by the
.gitattributes lfs rule; main stores this fixture as a raw binary blob
committed directly. Commit the exact blob so the regression harness reads
real frames and the file matches main.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(lint): keep the fallow audit gate green
Extract rootClassStyledSelectors so the subcomposition_root_styled_by_class
rule drops below the complexity threshold, and ignore the music-to-video
reference HTML (template + motion-primitive materials forked by path, not
import-graph reachable) — same treatment as motion-graphics/grounding.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: unblock music video ci checks
* docs: refine music-to-video planning catalogs
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: e-jung <8334081+e-jung@users.noreply.github.com>
Extract rootClassStyledSelectors so the subcomposition_root_styled_by_class
rule drops below the complexity threshold, and ignore the music-to-video
reference HTML (template + motion-primitive materials forked by path, not
import-graph reachable) — same treatment as motion-graphics/grounding.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous restore was re-filtered into a 130-byte LFS pointer by the
.gitattributes lfs rule; main stores this fixture as a raw binary blob
committed directly. Commit the exact blob so the regression harness reads
real frames and the file matches main.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Accidentally deleted by a prior `git add -A`; it is the golden output.mp4
the distributed regression harness diffs against. Restored byte-identical
to main.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>