* ci: a release PR runs a fast release check, not the heavy suites
* ci: keep the release check's header within the comment limit
* ci: shorten the release check's usage line
* ci: trim the release check's comments
* ci: the release check's classifier hands the manifest test to a helper
* fix(core): preview seeks land on the composition's own frame rate
* fix(studio): frame step, frame readout and ruler use the composition's frame rate
* fix(studio): an end trim previews the clip's last frame at the composition's frame rate
* refactor(studio): ruler labels follow the ticks' frame rate and fps parsing splits by form
* style(studio): format the ruler label helper and its test
* chore(inspector): raise the package size budget to the measured size plus headroom
* fix(producer): convert HEIC images to JPEG at compile time
Tiled HEIC photos probe as several video streams, so the media-type
preflight rejected them as image:video, and Chrome cannot decode HEIC.
Sniff the ftyp brands of every <img>, join and rotate the tile grid with
ffmpeg, verify the declared size, and swap in a cached JPEG.
* fix(producer): shorten the HEIC prep comments
* fix(producer): name host tool failures and keep a failed HEIC preview image as authored
Also: HEIC tests run in the integration lane; the primary grid is picked by default disposition.
* chore(producer): raise the package size budget for the HEIC prep
* fix(core): a composition behind a wrapper takes its host's start
Fixes#4334.
* fix(core): a composition inside a timed wrapper starts with the wrapper
The nearest host or timed wrapper above a mounted root sets its start, so its clock starts when the wrapper shows it, matching how CSS animations already pick their clip.
* test(producer): re-pin the test classification digest
* docs(examples): the inspiration page, with films and remixes to start from
The Examples page becomes Inspiration: two rows of films made with
HyperFrames, then 41 remixes of popular videos, each crediting and linking
the original post, with how to remix it in the Studio app. The rows play
one film at a time, since a second playing film drops the page from 120 to
30 fps, and they get a Pause button. The block and template tables stay.
* docs(examples): remixes honour reduced motion; one film while the dialog is open
Remix previews now follow reduced motion (read on the first render,
stopped if it turns on), and each film is a button that plays it on
purpose with sound. The film rows stop their previews while a full film
plays in the dialog, so only one film plays at a time. Two remixes get
the credit handles the app lists for them.
* docs(examples): a motion setting change keeps remix cards as they are
The shelf keeps the reduced-motion setting in a ref, so a change no
longer re-renders it and remounts every card: a playing remix, its
focus and its open instructions stay.
* docs(examples): one film at a time across the whole Inspiration page
The dialog paused only the film-row previews, so a chosen remix kept
playing behind it. Any video that starts with sound now makes each remix
card stop its own, and the film rows rest their previews while a film
with sound plays anywhere on the page.
* docs(examples): remix cards and film-row controls in the docs palette
The remix cards used Tailwind zinc greys and white, the film rows the old
green focus ring and fixed tints; they now use the palette tokens, so
they sit on the beige ground and in the Night theme like the rest.
* test(studio): real-Chrome tests inline GSAP so their page waits on no request
* test(ci): smoke projects inline GSAP so required checks wait on no CDN
* docs: one Troubleshooting page, four tabs, and the start pages folded
* docs: keep how Studio app updates arrive on the help page
Folding the Studio app troubleshooting page into /help dropped its
"How updates arrive" section: the update button, Settings > Updates
and the Mac menu route, the Automatic updates toggle, and what to do
when an update stalls or cannot check. It is back word for word; every
label is still in the app.
* feat(skills): a Remotion port carries Remotion's box-sizing rule so bordered boxes keep their size
* chore(skills): regenerate the skills manifest after the format pass
* fix(hooks): the skills manifest is hashed after the formatter has run
* test(studio): a drag is measured against the box the preview shows
* test(studio): another element is read bare until it is selected
* fix(core): preview audio fades play as ramps on the audio clock, so cuts no longer click
data-fade-in / data-fade-out were folded into one volume per 50 ms sync tick and applied as
a step (el.volume, or gain.value in the Web Audio transport), so a 20 ms fade never played
and each clip started and stopped at near full level: a click at every cut. The export
applies the fades (ffmpeg afade) and does not click.
The transport now books each clip's fades on a gain of its own when it schedules the
clip, for both decoded buffers and captured media elements: a linear ramp from where the
clip starts sounding up to its fade-in, and down to silence at its end. The sync tick hands
the transport the level without the fade (onElementVolume's new fifth argument, sent only
where a fade applies), so a booked fade is not applied twice. The plain el.volume fallback
keeps its stepped fade.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): one doc block on setElementVolume
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): keep the comment share where it was
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): booked fades hold through pre-roll, follow rate changes, skip unbounded ends
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: raise the producer unpacked budget to the measured size plus 1%
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* docs(studio): the browser editor in six pages, named the browser editor
* docs(studio): mute where the editor has it, and no solo control
The audio pages still described a solo control and a group-row mute that
the editor does not render. Muting is the speaker button on an audio
track (Mute track / Unmute track), the same toggle on a selected clip,
and the Muted switch; a group is silenced through its members' tracks.
The audio reference drops its solo section for the same reason.
* docs(prompting): the Prompt Guide as six topic pages
* docs(prompting): call the skill the way each agent does
The overview told every own-agent user to start with /hyperframes; Codex
uses $hyperframes, and other agents have their own way. The line now
names both and keeps the caveat the old page had.
* feat(core): tell which video files deserve a lighter preview copy, and at which size
* feat(studio-server): serve lighter copies of heavy playable files, a keyframe every second
* feat(core): the preview plays lighter copies of heavy videos below full quality
* style: shorten two comments
* style: shorten a comment
* style: shorten a comment
* fix(core): format the preview quality files and split the copy sizing for the complexity gate
* style: format the studio-server preview files
* fix(core): type the copy job's level as a preview quality
* chore(producer): raise the package size budget for the preview runtime growth
* docs(guides): media pages titled by the task people search for
* docs(guides): background fixes as a visible section; list color grading as an agreed deletion
* docs(guides): one page per kind of video, with its prompts folded in
* docs(guides): two tiles per row on What to make; list the folded prompt pages as agreed deletions
* fix(producer): reject regression audio stream mismatches
* chore: prepare conflict resolution against current main
* chore: preserve reviewed fix on current main
* chore: prepare conflict resolution against current main
* fix(producer): retain audio regression checks with current main
* chore: prepare conflict resolution against current main
* fix(producer): preserve audio presence checks on current main
* ci: re-run changed test files to catch flaky tests before merge
* ci: give the flake gate the full test workspace setup
* ci: share the test workspace setup between the Test lanes and the flake gate
* ci: split the flake gate main for the fallow complexity gate and format
* ci: run node:test files with node, skip uncollected files, bound the gate's run time
* perf(core): skip rendering preview scenes far from the playhead
* refactor(core): split the far-scene pass into small helpers
* perf(core): a first render reveals only the clips it reaches
* fix(core): reveal the clips holding first-rendered tweens, wherever their window is
* feat(core): a scene may load a project helper and wait for fonts and still swap
* perf(core): the preview mounts only the scenes near the playhead
* perf(core): a seek waits only for the scene it lands in; neighbours mount one per turn
* feat(studio): selecting a clip in an unmounted scene mounts that scene first
* fix(core): a source a play and a seek both need is fetched and decoded once
* fix(core): a scene mounting during play joins the mix without restarting what plays
* fix(studio): selecting a clip waits on a scene mount only in a preview that unmounts scenes
* fix(core): far scenes still mount after an edit, and an edit to a parked scene keeps its styles
* perf(core): web audio schedules a clip only as the playhead comes near it
* chore(studio): trim the scene hold helper's comments to its package's share
* fix(core): a scene the preview unmounts is freed, not kept by audio, caches or old roots
* fix(studio): an unmounted scene's rows and their state come from the markup it mounts with
* perf(core): the preview mounts scenes ahead of play and keeps recent ones mounted
* refactor(core): keep the far-scene neighbour rule private to its module
* style(core): format the far-scene neighbour rule
* perf(core): the preview unmounts scenes the playhead leaves, as kept ones slowed each mount
* fix(core): scenes that measure text, call their own animate or wait on font loads can unmount
* fix(core): each preview scene carries the helpers it loads, so it mounts on its own
* fix(core): a mounted scene shows though its stand-in was hidden by display
* fix(core): a helper scenes share runs once for the film, and a remounted scene keeps showing
* fix(core): a scene that also publishes its timeline under another id stays mounted
* feat(core): scenes that draw in 2D or chain the timeline they register can unmount
* perf(core): a play frame that draws nothing new skips the hidden-clip lookup
* fix(core): a GSAP file copied into a project counts as the library for scene unmounting
* style(core): drop an unneeded escape in the registered-chain pattern
* chore: core and producer size budgets make room for the preview scene window
* fix(core): a scene that draws on a 2D canvas stays mounted
* fix(core): a scene the preview unmounts lets go of timelines it built after a font wait
* perf(core): a scene leaving the playhead keeps its last rendered size, no forced layout at the cut
* refactor(core): the far-scene skip keeps last-render boxes in their own helper
* fix(core): a mounting scene never records the runtime's root timeline as its own
* chore(producer): raise the size budget to the measured size with the preview runtime inlined
* docs(core): keep the far-scene and scene-memory comments within the comment limits
* perf(core): style each hidden clip visible once ahead of its cut, so the cut frame does not
* fix(core): a seek into a scene still mounting as a neighbour waits for that mount
* chore(core): keep comment share and size budgets to main plus the measured core growth
* ci: run cli tests in their own job so runtime tests stop timing out
* ci: format the test aggregator needs list
* ci: keep the test aggregator needs on one line for the reachability check
* ci(reachability): read wrapped and block needs lists instead of rejecting them
* feat(producer): fail a render that cannot finish within its maxRenderMs budget
* chore(producer): raise the producer size budget to the measured size of the render time guard
* fix(producer): extract HDR video frames a window at a time so scratch disk stays bounded
* fix(producer): drop the added compositor comment for the Comments gate
* fix(producer): keep the HDR scratch read plain and fail the render when a window cannot be extracted
* fix(producer): settle a looped HDR source's frame count before capture starts
* chore(producer): raise the packed size budget by the windowed HDR extraction's measured delta
* fix(cli): a long video copy reports progress, resumes in 15 s pieces and stops only when stalled
* perf(core): a video copy's size box follows the stage within a fifth, not a half
* docs(cli): shorten two comments to the line limit
* fix(cli): the copy heartbeat drops a finished copy; size budgets follow the stall and progress code
* chore: size budgets rise by this change's measured bytes
* perf(core): mounting a scene no longer restyles it to look for raster hints
* perf(core): raster hints read inline hints directly instead of matching every style
* perf(core): raster hints read the stylesheets once per page check, not per batch
* perf(core): raster hints watch no attributes while nothing is marked or hinted
* fix(core): a copied or re-attached marked layer keeps being followed
* chore(producer): raise the packed size budget by the 336 bytes the raster-hint fix adds
The producer bundles the runtime. On main (aae32e43ed) it packs at 773560 bytes
(2821768 unpacked) and this branch at 773896 (2822665): packed rises by 336, unpacked by 897.
* fix(engine): software renders reach SwiftShader when VK_DRIVER_FILES pins a GPU
ANGLE (and Dawn) load SwiftShader by pointing the Vulkan loader at its ICD
through VK_ICD_FILENAMES. The loader ignores VK_ICD_FILENAMES while
VK_DRIVER_FILES is set, and still applies VK_LOADER_DRIVERS_SELECT/DISABLE,
so on a machine that pins its Vulkan driver that way a --no-browser-gpu
render gets ANGLE on the real GPU and assertSwiftShader refuses it.
Launch SwiftShader browsers without those three variables. Hardware
launches, and environments that set none of them, are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore(engine): raise the packed size budget by the 342 bytes launchEnv adds
The increase sits on top of main's engine budget, 456055 after #6194.
Main packs the engine at 435883 bytes and this branch at 436205.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(studio-server): undo and restore keep the media they delete or replace
* fix(studio-server): set-aside media is freed only after a whole undo or restore lands
* fix(studio-server): set-aside media does not count toward the history budget
* fix(studio-server): set-aside media keeps its own ledger apart from copied blobs
* fix(studio-server): a set-aside copy waits for every path that counts on it
* fix(studio-server): an unreadable history folder fails the read instead of looking empty
* fix(studio-server): history opens past a temp file a crash left behind
* fix(studio-server): trim a comment so the comment share holds
* docs(studio-app): show the app working, three new pages, today's UI
* docs(studio-app): correct what the app does today (fact-checked against both repos' main)
Script: audio goes to HeyGen to transcribe; cuts wait in Pending video edits until Send; today's toolbar; suggested
cuts. Continue from Claude Code: Mac only, inside an agent session, catch-up after that hand-off, real output.
GitHub Copilot added. 4K price, 10-minute sign-in, Auto as the default, real sign-in errors, motion-guide limit,
the right-panel row. Looping clips pause and respect reduced motion.
* docs(studio-app): hold the Share page until Share launches (desktop-share is at 0%)
* docs(studio-app): Home screenshot from a regular account on b467 (Make, Reference, Script)
* Revert "docs(studio-app): hold the Share page until Share launches (desktop-share is at 0%)"
This reverts commit 201070ac7b7a551c5515c1a770be4a5e8ab36ab9.
* docs(studio-app): Export link label names its page
* docs(studio-app): where-is-it clips re-recorded on a regular account, b467
Staff-only items are gone (Pit Crew Academy, View as a user, the HyperFrames agent, Learn). # opens tools by typing;
the tools list and the @ menu match a regular account; the import clip ends before the folder list.
* docs(studio-app): new Script clip on a regular account: Cut, Pending video edits, Send 1 edit, 0:22 to 0:17
* docs(studio-app): Draw clip re-recorded on a regular account (Send 1 edit to Claude Code, 1 min 52 s edit)
* docs(studio-app): Export clip re-recorded on a regular account (1080p, 16 s); cloud rendering as the app shows it
* docs(studio-app): Video 1 change-a-clip demo re-recorded on a regular account (Up to $1.09 shown before Make)
* docs(studio-app): Share clips re-recorded on a regular account; the page mentions HeyGen's Checking step
* docs(studio-app): first-video demo re-recorded on a regular account (question cards, Send answer, 29 min storyboard, 10 min build)
* docs(studio-app): Reference page follows today's flow (the app reads the video, then Copy editing style or Video brief; What to swap); demo re-recorded on a regular account
* docs(studio-app): autoplaying demos start muted so browsers play them; Automatic updates in troubleshooting
* docs(studio-app): update home.jpg in place instead of replacing it
* docs(snippets): DocsVideo stops an autoplaying loop when Reduce Motion turns on mid-visit; play loads it again
* docs(studio-app): motion guides need the person's picture; drop a pending cut by deleting its row; remove two unused stills
* perf(engine): a remote source's tail decodes run many at a time, not one per half-core
* perf(producer): the plan logs where its extract stage spends its time
* perf(engine): a window that ends inside its source takes its frame count without a decode
* test(engine): pin the tail-decode slot test to the windows that decode
* style(engine): format the tail-probe window check
* test(engine): deferred counts match extraction on a B-frame NTSC source; VFR is not deferred
* style(engine): format the tail-probe tests
* perf(aws-lambda): a site stores each video's ffprobe JSON, and the plan reads it instead of probing
* test(aws-lambda): a site stores and hands out each video's probe
* style(aws-lambda): format the probe-facts upload and tests
* perf(engine): a site's stored probe also holds the final-frame timestamp the plan would probe for
* test(engine): drop an unused import
* test(aws-lambda): the stored probe holds the ffprobe JSON and the final frame
* test(aws-lambda): the stored probe holds the ffprobe JSON and the final frame
* perf(producer): a distributed chunk encodes while it captures
* perf(producer): the audio task logs where its mix spends its time
One line per audio stage: prepare, groups, mix, master, encode and the peak check, in ms.
* perf(engine): locked warmup ticks stop sleeping once the page is ready
* perf(engine): a window reaching a stored video's end takes its frame count from stored facts
* fix(producer): the cloud audio task logs its phases
* perf(engine): stored facts keep the last packet length so late-start windows skip the tail decode
* fix(engine): a probe spawns without waiting when no stored facts are declared
* chore(aws-lambda): shorten comments over the line limit
* fix(engine): warmup keeps pacing after a failed tick; stored final frames check ffprobe major
* test(engine): the recorded ffprobe major matches the build's own banner
* test(engine): the version-banner probes go through a named binary like the other spawns
* fix(studio-server): a history restore refuses to remove every tracked file
* test(studio-server): compare the held clip by bytes so the adopting check stays fast
* fix(studio-server): history's own hidden records do not count as files a restore keeps
* refactor(studio-server): name the restore guard's check instead of commenting it
* chore(studio-server): re-baseline the packed size budget for the restore guard
* feat: check names the filter fallback, the render log names the tier, Studio stops opting out
hyperframes check reports motion_blur_filter_fallback (info) with the reason capture could not
run and the way back to it. The engine's per-session log line gains tier=, and its pixel test
covers the fallback over a WebGL page. Studio no longer sends previewMotionBlur: every Chrome
can preview blur now, through the fallback where it lacks html-in-canvas; the server still
accepts false from an older Studio.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* feat(core): blur picks the cheaper filter tier when every mover only translates
The scan now notes whether any mover turns or scales (the linear part of its transform chain
changes) and whether it already forms a stacking context while it moves. When every mover only
translates, the filter tier draws as well as capture and costs about what no blur costs: on an
8 s bench shot a render took 7.0 s against 9.9 s with capture and 7.3 s with no blur. Anything
that turns or scales, or moves without a transform, stays on capture.
data-motion-blur-tier="capture" or "filter" forces a tier. The report says which one drew
and why (translation-only, tier-attribute); hyperframes check stays quiet for those two, which
are choices, not fallbacks. The pixel tests run the shared assertions through both tiers.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* docs(motion-blur): the two tiers, the tier attribute, and what changed in limits
The guide described blur turning off for WebGL, a root transform and similar. It now says the
filter tier draws instead, how the tier is chosen and forced, what each does with a turn or a
zoom, the measured cost of each, and which findings check emits. The skill note drops the
off-for-WebGL advice and the 1.7x figure.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* feat(core): blur defaults match a 240 degree shutter and at least 32 samples
The shutter opens 240 degrees by default (was 180) and each mover gets at least 32 taps (was
sized to the smear alone, down to 2), growing with the smear as before. These are the settings
the shutter blur Jake built uses; on the card whip the default render is now 51 dB from his
(34.6 dB at the old defaults). The guide's attribute table says so.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* chore(core): trim tier.ts comments to the ratchet
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* chore: allow deleting the Studio previewMotionBlur render test
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* feat(core): capture blur falls back to per-element filters where capture cannot run
html-in-canvas cannot capture a WebGL canvas, an iframe or a transform on the root, and some
browsers lack it. Blur used to turn off for the whole composition. Now the runtime hands the
composition to a filter tier: an SVG filter on each mover, 32 offset copies averaged along its
sampled path, softened along the motion so long smears do not step. It filters the live DOM,
so it works over a WebGL canvas, in any Chrome, and leaves glows and shadows on the element.
Turn and scale only soften; capture draws those truthfully, so it stays the first choice and
a scene re-init tries it again.
Capture turning itself off for a defect or three failed captures hands over; the renderer's
explicit off (HDR, distributed, engine blur) stays off. The report names the tier. Core's
unpacked size budget goes to 3,965,000 for the tier.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* test(engine): the backface pages now take the filter tier
The two backface tests still expected blur off; with the filter tier a
backface page reports tier filter. Fails on CI without this.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(cli): check copies hostile videos at the composition size, not the source size
* fix(cli): the check server serves the copy the page names with hf-proxy-box
* feat(cli): check --prepare-media makes the video copies alone, and a stopped copy resumes
A long source is copied in one-minute pieces that stay in the cache, so a copy
that is killed or runs out of time continues from its finished pieces.
check --prepare-media runs only that step: no browser, no report, a per-file
result as JSON, and exit 1 when a copy failed.
* fix(studio-server): keep finished copy pieces while their copy runs
The short limit for dead partial copies would delete the first pieces of a
long copy that is still encoding. Finished pieces now follow their own
one-hour limit and are never swept while their copy is in flight.
* refactor(studio-server): shorten two comments
The comment gate limits a comment line to 120 characters and a run to four lines.
* refactor(studio-server): drop comments the names already carry
The comment ratchet lets a file's comment share only go down.
* fix(cli): the check server pre-warms the copy the page asks for, not the source-size one
The server started a source-size copy of every hostile video next to Chrome on
each check, because its pre-warm passed no box while the page and the up-front
copy use the composition's box. The pre-warm now takes the same box, found by
the one helper the up-front copy uses.
* fix(cli): end a check on no progress, not on a 120 s wall clock
The check ended 120 s after Chrome was ready no matter what it was doing, so a
long project whose sampling and overlap pass each kept working still failed
(the overlap pass was one page call that reported nothing for its whole run).
The deadline is now an idle clock: every phase entered, every sample finished,
every overlap batch of eight times and every operation that settles restarts it,
and the check ends only after 120 s with none. The overlap grid is asked for in
batches so it can report between them.
* refactor(cli): shorten the idle deadline comment
The comment gate allows 120 characters on a comment line.
* test(cli): check's overlap grid runs in page tasks of at most 8 samples
* fix(cli): check test types for the cli lib, studio-server size budget for resumable copies
The budgets are the exact packed sizes people last measured, so a few hundred bytes of ordinary code failed Build. The exact budget stays the number to bump and the 15% ratchet stays; only the over-budget comparison gets 1% of tolerance.
* fix(engine): encode with the same x264 and x265 thread count on any host size
libx264 and libx265 pick their thread count from the host core count when
ffmpeg is not told one, and the bitstream depends on it, so the same frames
encoded to different bytes on 8, 16 and 32 vCPU nodes and chunks of one
render could differ. Both encoders now pass the thread count from the CPU
quota (Node reads the container limit), which is fixed per pod.
* chore(engine): raise the packed size budget for the encoder thread module
* fix(aws-lambda): keep the arm64 image staging script out of the published package
stage-arm64-image.ts is run from a checkout by the image workflow and the publish job, and nothing in dist or the CLI imports it. With the release's prebuilt/ files added, aws-lambda measured 73484 bytes packed and 267013 unpacked in the publish job, over its budgets of 73369 and 266805, so v0.8.187 did not publish.
* ci: the packed-manifest check pins stand-in Lambda assets, like the publish job
The publish job adds prebuilt/handler-zip.json and prebuilt/video-image.json before verify:packed-manifests; the PR check did not, so a package could pass on the PR and fail at publish. The verifier now runs the same pin script on sparse stand-ins of release size when no pins exist, and removes them afterwards. It lives in the verifier, not a Build step, so the edit-accuracy input closure stays unchanged.
* fix(core): capture blur sizes its taps to the smear and weights them exactly
A fast move strobed at the default 16 taps (8 px steps on a 1560 px whip). With no
data-motion-blur-samples set, taps are now about one per 2 px of travel (2 to 128); an
explicit samples count still wins.
Canvas quantizes image globalAlpha to 1/255, so 1/n weights left coverage short (n = 104
summed to 0.81, a lighter card body). Each tap now carries a whole number of 1/255 units
that sum to exactly 255.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* feat(core): capture blur draws the movers from their own transparent layer
The crop came from a capture of the whole scene, so the backdrop under a see-through mover
smeared with it (a lighter box with the dotted background erased) and a glow or shadow
outside the mover's box was cut off, leaving a hard-edged box.
Each frame now captures the movers alone, everything else visibility:hidden, so a mover's
pixels carry their true alpha and no backdrop; the background capture is unchanged. The crop
grows by the mover's ink reach (box-shadow, text-shadow, filter, outline, measured during the
scan), so glows travel with it. Two paints per frame, as before.
The output is cleared on wrap instead of showing the previous wrap's last frame, since the
first paint is no longer a drawable sharp frame. Core's unpacked size budget goes to 3,950,000
for the new module.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(core): capture blur finds callback-driven movers when events are on
The mover scan seeked with callbacks suppressed even where the root allows them, so a
composition whose motion lives in a timeline callback (a render(t) driven from onUpdate)
reported 0 movers and rendered sharp. With data-motion-blur-events="on" the scan now seeks
the way the frame reads do. Without it nothing changes: callbacks stay suppressed.
The no-movers finding in hyperframes check now names the attribute.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* feat(aws-lambda): push a prebuilt image to ECR without Docker
Adds pushImageToEcr and ensureEcrRepository to the SDK subpath. The push
reads a single-platform OCI image layout, verifies every blob against its
descriptor before any ECR call, uploads only the blobs ECR lacks through
the layer-upload API, and puts the manifest under a tag, returning the
digest-pinned image URI.
* fix(aws-lambda): keep the ECR client out of the SDK bundle
Like the other AWS SDK clients, @aws-sdk/client-ecr is a dependency the consumer
resolves. Bundled into the ESM SDK it hits a dynamic require of node:https and
the packed package fails to import.
* feat(aws-lambda): build the ECR client from a region like the other SDK calls
* build(aws-lambda): add only the ECR client to the lockfile
* fix(aws-lambda): adopt an existing ECR repository only when it carries the caller's tags
* chore(aws-lambda): raise the size budget for pushing an image to ECR
pushImageToEcr ships in dist (unminified chunk plus its .d.ts); no source maps, tests or release-only files are involved. Built dist, npm pack --dry-run, main versus main plus this branch: +2486 B packed, +8846 B unpacked. The merge-group run measured 73369 packed and 266805 unpacked, which are the new budgets (were 70874 and 258166).
* feat(aws-lambda): arm64 video function image from the handler ZIP
The image is the release handler ZIP with its x86 ffmpeg and Chrome swapped for
the native ffmpeg tree and the sha256-pinned Sparticuz arm64 Chrome pack, so the
x86 and arm64 functions run the same handler code. The image stage only copies,
so any host builds it. CI builds it and runs ffmpeg, a real Chrome render and the
Lambda runtime on an ARM runner.
* fix(aws-lambda): launch Chrome in the image smoke the way Lambda does
@sparticuz/chromium inflates its AL2023 libraries only when AWS_EXECUTION_ENV
names a Node runtime; Lambda sets it, a bare docker run does not. The workflow
also runs on @sparticuz/chromium bumps and handler ZIP layout changes, which
re-stage the image.
* fix(aws-lambda): smoke the arm64 image through its own Lambda runtime
The smoke is now a handler invoked through the image's entrypoint, whose
bootstrap sets AWS_EXECUTION_ENV the way Lambda does, so @sparticuz/chromium
takes its Lambda branch with nothing set by hand. It returns the value it saw.
* fix(aws-lambda): agree the move of the ffmpeg build workflow into the image workflow
* fix(aws-lambda): build the Chrome pack URL from the pin and hand the checked pack to tar in memory
* ci(windows): fail a hung test step at its deadline and name the process that hung
* ci(windows): upload the test step log even when the step is killed
* fix(cli): preserve video frames when scenes reuse element IDs
* chore(core): keep media identity comments within the ratchet
* test(core): update page-build goldens for distinct media ids across repeated scenes
* fix: data-volume multiplies the volume lane in preview and render
* fix: a clip's data-volume plays ahead of its audio effects, in preview and render
* feat(studio): the media panel shows a clip's Gain beside its Volume level
* fix(audio): cap gain times volume lanes through each ramp
* chore(core): account for shared volume envelopes in package budget
* fix(core): restore static gain when a playing lane is removed
* docs(cli): document media-use and usage commands
Both command groups are registered and visible but had no section in the CLI reference.
- media-use: subcommands, resolve flags, the per-type source order, where files land,
which sources need a HeyGen sign-in or can cost money, and telemetry.
- usage: flags, the known and unknown result shapes, which logins it reads, and that it
sends no telemetry.
- Add both to the "Find your command" table.
The from, reuse, and params subcommands cannot take their value in this release, so the
page points to the working resolve --from, --reuse, and --params forms instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(cli): drop the media-use bug workaround note
The bugs go to the media-use owner instead of living in the docs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs(cli): list every command in the finder, and check it in CI
A ~20-line check fails the docs workflow when a CLI command is missing
from "Find your command". On main it finds media-use, usage, models,
clean and events. AGENTS.md and CLAUDE.md ask agents to ship docs with
the change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: format the docs command check
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): a clip's volume lane stays smooth in preview while the page is busy
A clip with a volume automation lane had two writers on one gain in
preview: the Web Audio transport scheduled the lane as ramps on the
clip's gain, and the per-tick media sync wrote the same lane's value onto
that gain about 60 times a second. Each write landed whenever the main
thread got to it, so a busy page stepped the gain and the clip sounded
choppy. The lane now has its own gain node, like a group's fader beside
its mute gain, and the per-tick volume of a routed clip no longer folds
the lane in.
* fix(core): name the transport's ownership of a routed clip's lane instead of commenting it
* chore(core): raise the unpacked size budget for the volume lane's own gain
The preview fix gives a clip's volume lane its own gain node and tears it
down with the clip, which takes core to 3935950 bytes unpacked, 283 over
the budget main set at its exact size.
* fix(core): volume lanes follow rate changes and edits mid-play
* fix(core): shorten the clip stages comment
* fix(core): point the volume lane comments at the code they name
* fix(ci): packed consumer installs keep same-day dependency bumps on every Bun release
Bun 1.4.4 swapped the quoted package and range in its minimum-release-age
error, so the retry excluded the range instead of the package and the
install failed again. The package is now the token that bun's
"<name>@<range> failed to resolve" line pairs with the other one, and an
unrecognised message fails visibly instead of excluding the wrong name.
CI installs the newest Bun when nothing pins it, which let the 1.4.4
release turn every PR's support tests red mid-day. engines.bun in the
root package.json pins it for every setup-bun step that does not set a
version (packageManager would make corepack's pnpm refuse the repo).
* fix(ci): keep the release-age parser's doc comment to one line
* fix(cli): keep whisper CJK tokens as separate timed words
whisper.cpp marks word boundaries with a leading space, but Chinese and
Japanese tokens never carry one, so parseWhisperCpp merged every CJK token
into the previous word, across segments too. A 9.5-minute Mandarin episode
came out as two "words" of about 280 s each, leaving captions and
sentence-building nothing to split.
A token now starts its own word when it crosses a CJK boundary, full-width
punctuation still attaches to the word before it, and cues end at 。!?.
* fix(ci): capture package name in release-age exclusion