* fix(core): Safari audio that sits frozen after its cut is pulled onto the playhead in 0.4 s, not 1 s
* fix(core): shorten the frozen-audio comment
* fix(core): leave a WebKit audio element that is still filling data out of the frozen rule
* fix(cli,skills): say a step's credits are its total, and keep cost out of retries
An out-of-credits refusal named what the step needs, and an agent read
that as the shortfall. It now says the number is the step's total.
After its last try, an agent told the person what was spent and what
another try would cost. The HeyGen reference now says not to.
* fix(cli): plan avatar credits from the HyperFrames price rows
An avatar's credits came from its old API price, about five times what
the plan charges, so --max-credits refused calls that fit and a top-up
asked for too much. They now use the Avatar IV photo row plus the
general avatar video row, 950 credits a minute.
* fix(cli): pin the avatar price test and keep pricing under the comment share
* fix(cli): plan avatar seconds on the Avatar IV row alone
The general avatar row is only checked at admission and billed on
seconds left after Avatar IV, so adding it to every second made a 15 s
script plan at 238 instead of 200 and a 220 limit refuse a call that fits.
* feat(skills): creator templates on the HyperFrames plan
A Pro account asks for a video in a creator's format and the agent pulls that
template on demand. The packs sit in the private bucket behind the plan-gated
bundle route, so a Free account gets a 402 and the upgrade step. A pull costs
no credits; making the film spends them as Reverse does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): make creator-remix the one template skill
SKILL.md holds the catalog, the Pro openings, the pull and the credit
rules. references/remix.md is how to make the episode, from Desktop's
creator-remix with only what the app alone has swapped for the CLI.
The packs now hold only the template, so their hashes change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): the creator-remix skill and its packs
SKILL.md holds the catalog, the Pro openings, the pull and the credit
rules. references/remix.md is how to make the episode, from Desktop's
creator-remix with only what the app alone has swapped for the CLI.
The packs now hold only the template, so their hashes change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(skills): word creator-remix's sign-in steps the way media-use does
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): a creator-remix presenter runs on the plan by default
The person's own HeyGen avatar is recommended, not required: without
one, 小Lin's presenter is made with HeyGen Video 1 from their photo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cli): pin template packs whose voice bills the plan first
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): offload media named inside template tags
References in a composition's <template> were never rewritten, because the DOM
query stops at the tag and template.content does not serialize, so the file stayed
in the zip. A stem matched as a plain word and a same-named file elsewhere counted
as a mention too. A 5-minute presented film went from 196 MB to 71 MB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cli): lipsync, and avatars that speak a finished voice
Video 1 takes audio as a reference and re-paces it, so a shot's lips follow its
own track and drift up to a second from the voice. generate --type lipsync redoes
the mouth to the exact voice. generate --type avatar takes --audio, and --avatar
for the person's own look, so the lips land on the voice from one job.
Also moves the plan rows to the 2026-10-30 price book, estimates a voice's credits
so --max-credits can hold it, logs jobs from a folder with no project, and describes
the resolve flags.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): presenters go avatar first, and a voice can be stock
The person's own avatar, then Avatar IV from their photo, then Video 1 with
lipsync. With no clone or recording, a stock voice named in the credits. Pins the
five rebuilt packs. The archive test keeps its ../ under GNU tar with -P.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(cli): fix avatar price and sfx provider tests, trim comments
A wav gives the avatar price test an exact length on every platform. The
sfx tests now expect HeyGen first. Comment share is back under the base.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): read a font shorthand whose size nests parentheses
`font: calc(var(--size) * 1px)/1 "X"` cut at the first closing parenthesis, so a
bad family name failed the render.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): one ledger row per job, and search skips a template's own pictures
A job done at once logs one row, transcribe logs to the project, and resolve no
longer offers a component's file for a search. --reuse and --provider get help text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): pin the tested template packs, and a board needs no voice
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(cli): read the transcribe ledger row as JSON, for Windows paths
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the Big Questions pack without the film length check
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(skills): call them style templates, and the docs example pulls a real slug
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the cleaned template packs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(skills): drop the stock-presenter ban and the no-spend-before-storyboard rule
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the Paper Trail and Long Story Short packs without the stock-presenter ban
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): rename creator-remix to style-remix, and the five templates to names that say the look
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): style-remix lists the five templates when none is named
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the Finance Explainer pack that varies its motion graphics
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cli): media-use generate --type cutout takes a shot out of its background on the plan
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the Finance Explainer pack without the provisional variety rule
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): pin the five style packs with variety rules measured on real films
Animated Science gets a worlds-and-boards budget, 3D True Crime a main board and a
brief corner note, 3D Brand caps by measured rate, Finance and Collage set pieces and
more than one look.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(skills): media-use names the on-plan cutout and lipsync commands, not the heygen CLI ones
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(cli): cutout retries dedupe on request_id, uploads carry their real type; style-remix is public
The background-removal route dedupes on the body's request_id and ignores the Idempotency-Key
header, so a retry after a lost answer started a second job: runJob now also sends the key in the
body for such a route. Uploads typed every non-MP4 file as audio, so WebM and MOV inputs were
refused: they now take their type from the extension. Fails without the fix: the new cutout test
in generate.test.ts sees audio/mpeg for WebM and MOV.
style-remix drops metadata.internal and is listed with the other published skills.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(cli): keep the comment share of the touched files at main's
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: count style-remix in the README and CLAUDE.md skill lists
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(skills): rename style-remix to style-sampling
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): keep overflowing content and SVG clip paths in motion-blurred frames
A blurred mover was copied only within its own layout box, so text wider
than the box (a one-line wordmark in a fixed-width container) was cut in
blurred frames. The movers-alone capture also hid every non-mover, including
the SVG clipPath a mover references, which empties the clip and makes the
mover vanish. Both showed as flicker during playback and in full renders.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(core): keep the motion-blur fix within each file's comment share
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(core): measure overflowing text with a Range, ignore rects inside the mover
A block child is as wide as its mover however long its no-wrap text, so
reading child boxes missed the common overflow layout. Read the text's own
rects through a Range. On a rotated mover each child's upright client rect
covers the mover's corners, which doubled the box at 45 degrees and could
pull a neighbouring mover into the trail; only rects that leave the
mover's own client rect now grow it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(core): measure a rotated mover's overflow unrotated
The upright client rect of a rotated mover has empty corners, so overflow
that landed there passed the "stays inside" check and was still cropped.
Measure a rotated mover with its own transform off and map the rects back
through its layout pose, then restore its style.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(core): keep a rotated mover the containing block while measured
With only transform: none, a mover with no position stops being the
containing block of its absolute and fixed children, so they jump to an
outer ancestor while measured and their real overflow is cropped. Add
will-change: transform during the swap, and skip the swap when the mover
has no transform of its own.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(studio-server): the preview records the kind and origin of each early error
* fix(studio-server): write preview script values with U+2028/2029 and < escaped
* fix(core): preview playback lands where a seek does when a set interrupts a tween
* test(core): the preview overlap fix holds under a nested time scale and fires each callback once
* style(core): shorten the overlap comment
* refactor(core): the overlap span names its fields and carries no comments
* fix(core): motion blur notices a WebGL canvas the Studio page touched first
When a same-origin host page (Studio) reaches a preview element before the
scene does, for example through a MutationObserver record, Chrome gives that
element the host's prototype. The scene's getContext("webgl") then ran the
host's unpatched getContext, so the canvas never reached the accelerated
canvas set, capture motion blur stayed on, and the WebGL canvas froze during
preview playback.
The tracker now records a canvas in the set of the canvas's own window, and
installs itself in a same-origin parent too, compiled with the parent's own
Function. Cross-origin parents are skipped.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(studio): track accelerated canvases in Studio's window, not the embedder's
The preview runtime no longer installs the canvas tracker in its parent
window. That ran on every preview boot, so any same-origin page embedding
the player got its getContext wrapped, and a host CSP without
unsafe-eval reported a violation on each load. Studio now installs the
tracker in its own window when its Player module loads, and core
exposes it at @hyperframes/core/page-motion-blur.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* chore: keep the comment share of the touched files from rising
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(preview): an unplayable video shows its own frame while its copy is made
* fix(preview): drop the comment on the copy's light flag
* fix(preview): shorten the still helper's doc comment
* test(preview): each proxy-wait test starts with no copy report left over
* fix(preview): cached still frames are swept with the proxy copies
* fix(core): a canvas in a blurred composition stays where it was laid out
drawElementImage drops a nested canvas's layout offset unless the canvas
has a transform, so with capture blur on, `top: 200px` painted at 0. A zero
`translate` while captured is enough, and leaves `top`, the composition's
own `transform` and its `translate` alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): give the nested canvas rule no specificity
A class on a composition's canvas lost to the rule, so its own `translate`
was dropped while captured. Under `:where()` any composition rule wins.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): a motion blur worker starting mid-film keeps the right state
The mover scan walks the whole film with callbacks live, then seeks back to
the worker's start. GSAP fires one-off calls in both directions, so a worker
starting between two calls took the later call's state and held it until the
next call. Going through 0 first gives every worker what frame 0 sees.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): rewind in the mover scan, so the filter tier gets it too
Both tiers call scanMovers and then seek to the worker's start. Rewinding
inside the scan covers them both, including the rescan after a tier handoff.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): preview repaints and rebuilds effects after the GPU restores a lost WebGL context
* fix(core): a paused scene redraws and a playing one repaints effects after a context restore
* test(core): a paused scene is redrawn after a WebGL context restore
* test(core): give the restore redraw test a clip
* fix(core): shorten the context recovery header comment
* fix(core): rebuild effects only after every lost context has restored
* test(core): the restore rebuild waits while another context is still lost
* test(core): count context requests to show the rebuild waits
* test(core): the mock reports the lost flag the test sets
* fix(core): WebKit audio seeks to its in-point before its cut, and a stuck one resyncs in 1 s
* fix(core): shorten the WebKit stuck-audio comment
* fix(core): let the stuck-audio constant name stand without a comment
* fix(core): iPhone Safari keeps one connection per file for the take on screen and the next
* fix(core): drop the doc comments the comment ratchet counts in studioPreviewMark
* fix(engine): a scene that has ended gives up its WebGL context so the next scene is not slowed
* build(engine): raise the engine package size budget for the WebGL release module
* chore(engine): shorten the WebGL release comment
* fix(engine): a scene gives up its WebGL context only when the runtime says its window ended
* chore(engine): drop a comment the type already says
* chore(engine): shorten a comment line
* feat(core): make a heavy clip's lighter copy ahead of time when the host asks
* feat(core): keep the comment share of the proxy transcoder where it was
* feat(core): split the copy-ahead choice so each part stays simple
* feat(core): the preview route passes the copy's urgency as the proxy options expect
* fix(core): a preview video short of data holds the film instead of looping snippets
* fix(core): keep the comment share of the runtime files where it was
* fix: cache paths follow HYPERFRAMES_CACHE_DIR
Every place HyperFrames keeps downloaded or built files now takes its
folder from one helper, hyperframesCacheDir() in @hyperframes/core. The
default stays ~/.cache/hyperframes; HYPERFRAMES_CACHE_DIR moves it.
* fix: register the cache-dir subpath for bun and node, and build inspector from core source
* test: the cache-dir override test uses a folder that is absolute on every platform
* fix: the opencv cache follows HYPERFRAMES_CACHE_DIR
* fix(cli): npm installs run in their staging dir, so no cache path reaches a command line
* fix(cli): whisper.cpp is cloned inside its build dir, so no cache path is a git argument
* test(cli): the whisper clone test finds no system whisper on Windows either
* fix(core): accept 65-point .cube LUTs, the size grading apps export
* fix(core): shrink LUTs wider than the GPU texture limit instead of rendering black
* fix(cli): check and snapshot free video clips the playhead left, halving memory on cut films
* fix(core): drop the releaseMedia doc comment that raised the file's comment share
* fix(core): a resized preview video keeps its picture while its new copy loads
Swapping a video to a copy made for its new size emptied it until the copy
decoded, so the preview showed black for a moment. The swap now shows a still
of the frame it had, as the video's poster, until the new copy can draw.
* fix(core): a held preview still is let go when no file loads back
* chore(core): raise the packed size budget for the held preview still
* feat(core): WebKit plays bare audio from a decoded buffer on the audio clock
* fix(core): a handed-over WebKit track goes back to its element when the speed changes
* test(player): play the WebKit audio edits in real Safari on a macOS runner
* fix(core): Safari keeps playing edits' audio without seeks or rate changes
* fix(core): keep the 0.1 s audio sync bound outside WebKit
* fix(core): restore main's audio steering outside WebKit
* fix(core): tell WebKit from the test DOM's user agent
* fix(core): name WebKit's audio start lag instead of commenting it
* fix(core): count embedded WKWebViews as WebKit for audio sync
* feat(core): the playback health reading says which clock the film followed
* style(core): format the clock's new reading
* refactor(core): let the clock's names say what the comments said
* fix(core): a seek that never settles ends after 30 s and names what it waits on
* fix(core): keep the seek-wait comments within the comment limits
* fix(core): only a caller's limit ends a seek wait; check and snapshot end it before their timeout
* fix(core): style a sub-composition root written as #root[data-composition-id]
* fix(core): keep an anonymous host's root styled when #root[data-composition-id] is scoped
* fix(core): scope a compounded authored root as a selector list so pseudo-elements stay valid
* fix(core): Safari stops chopping audio by seeking it over and over during playback
* test(player): the WebKit playbench also counts audio seeks and frozen audio
* fix(core): audio still catching up is seeked once its lag passes hard sync