Commit Graph
6001 Commits
Author SHA1 Message Date
Rudy Mizrahi Celekli 84e6dfd247 fix(cli): exclude rendering flags from tween tail warnings (#5711) 2026-10-24 00:28:10 +00:00
kevin c478110be7 fix(engine): redraw a small drawElement frame before failing the serial capture (#5748)
The serial guard in captureFrameSurface, which segmented capture reaches, threw on any frame under the
size floor. It now draws the frame once more and keeps a byte-identical redraw, as the streaming drain
guard does, so a flat frame no longer fails a segmented render.

Fixes #5685
2026-10-24 00:28:06 +00:00
Meghna VasudevaandClaude Opus 5.5 fb8aadad17 docs(cli): apply review notes from #5442 to open, timeline and history (#6110)
Note that the desktop hand-off in `open` is macOS-only for now, that
`timeline apply --plan` prints only a count without --json, and what
`history undo --who` reverts.

Refs #5386

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 00:27:59 +00:00
Miguel Ángel 179098936f fix(core): preview fetches only the clips due next, and drops passed clips still downloading (#6160)
* fix(core): preview fetches only the clips due next, and drops passed clips still downloading

A fast-cut film started a fetch per clip inside the look-ahead and let passed clips finish.

* fix(core): a passed clip stays released for the rest of the play

It was refetched each pass inside the look-back window.
2026-10-24 00:15:24 +00:00
Miguel Ángel 19cc96e3f2 test(engine): only h264 is held to identical bytes across host sizes (#6167)
x265 output varies between runs on one host, so that case flaked the merge queue.
2026-10-24 00:15:10 +00:00
Vance IngallsandClaude Sonnet 5.5 e077d9dc74 fix(core): a hidden child of a mover stays out of the blur trail (#6155)
The isolation CSS re-showed every descendant of a mover, so a child the
author hid with visibility:hidden was cropped and smeared into the trail
(240 green px in the repro; 0 in the sharp frame). The hide rule now skips
the mover subtree and the mover alone is made visible, so its descendants
inherit and keep their own visibility.

Found by review of #6106.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 17:37:41 -07:00
Vance IngallsandClaude Sonnet 5.5 ffc327b44c feat(studio): motion blur panel with Off / Subtle / Strong (#6112)
* feat(studio): motion blur panel with Off / Subtle / Strong

One three-way control in the right dock. Subtle is the default blur
(240 deg shutter, samples sized to the smear); Strong is 720 deg and 64
samples. Writes the root data-motion-blur attributes through the SDK so
renders match and undo works; reads live attributes from the preview.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(studio): motion blur panel edits the file's own root

The edits were worked out against the preview DOM but written to the file,
so a stale preview (undo inside the hold, a slow reload) could save a level
other than the one picked. They are now computed inside the mutation from
the candidate's root. While locked to a sub-composition no level is shown.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(studio): shorten the motion blur tab so the dock strip fits

Five tabs overflowed the default dock width by 10px and clipped a label
(Studio: load smoke). The tab is now titled Blur.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 17:29:42 -07:00
Jerrai d5526bd480 docs(skills): remove duplicated sections in skill references (#6164)
- skills/hyperframes-cli/references/upgrade-info-misc.md: the "Remaining
  harness usage" section (heading plus three paragraphs) appeared twice in
  a row; removed the second, byte-identical copy (8 lines).
- skills-manifest.json: regenerated the hyperframes-cli content hash, as the
  manifest check requires after a skill file changes.

No wording changed.

— Jerrai
2026-10-23 23:52:28 +00:00
Miguel Ángel 44100a5370 fix(sdk): a kept delete drops its media sources and a hidden video is not waited for (#6163) 2026-10-23 23:24:04 +00:00
Miguel Ángel 579f73bf29 fix(studio): audio clips in the timeline share the video clip corner radius (#6159)
The audio clip radius token now follows the clip radius.
2026-10-23 23:16:02 +00:00
Miguel Ángel 3439433d67 perf(aws-lambda): a render reads a video's audio from a small file stored beside it (#6162)
* perf(aws-lambda): a render reads a video's audio from a small file stored beside it

A video's audio is interleaved with its pictures, so mixing it from the cloud read the whole video (2.5 GB for a 20-minute 4K film) at the Lambda's network speed. lambda sites create now stores each large video's audio packets as a separate object keyed by the video's hash; the audio task reads that when it exists.

* chore(producer): shorten the audio sidecar header comment

* fix(aws-lambda): no audio sidecar for a video whose audio starts late or that has several tracks

* test(producer): format the audio sidecar tests and end their ffprobe options with --
2026-10-23 23:12:52 +00:00
Miguel Ángel 1552f52fbe fix(cli): snapshot waits for the image layers a seek reveals before capturing (#6161)
* fix(cli): snapshot waits for the image layers a seek reveals before capturing

x

* style(cli): shorten the late-image test comment

* style(cli): trim three comments so the comment share of the capture file goes down
2026-10-23 22:55:36 +00:00
Miguel Ángel 8df9b6dd3b fix(engine): a composition whose script throws is skipped with a warning, not refused (#6156)
* fix(engine): a composition whose script throws is skipped with a warning, not waited on and refused

A script that threw before registering its timeline kept the capture waiting the full 45 s readiness timeout, then the render was refused. After a page error with timelines still missing the wait now ends after a short grace, each such composition gets an empty paused timeline of its authored length, and a script_error_isolated warning names the element. The render goes on without that animation.

* test(engine): memo test expects a script failure, format the browser test

* style(engine): shorten a doc comment to the line limit

* fix(engine): isolate only nested compositions that threw, never load failures or a root

* fix(engine): a thrown composition script fails fast and names the composition, not a load error

* fix(engine): the page-error grace starts at the first error, not at the start of the wait

* fix(engine): a nested throw is skipped only while another nested timeline is live
2026-10-23 22:33:05 +00:00
Miguel Ángel 55cbc0e158 feat(cli): check writes a progress line to stderr per sample, overlap batch and craft frame (#6151)
* feat(cli): check writes a progress line to stderr per sample, overlap batch and craft frame

* fix(cli): the check progress lines are written by the CLI, not by the pipeline tests call
2026-10-23 22:14:44 +00:00
Miguel Ángel dad8259457 fix(sdk): a stored delete a script still looks up is hidden when overrides replay, not removed (#6143)
* fix(sdk): a stored delete a script still looks up is hidden when overrides replay, not removed

* fix(sdk): shorten the script lookup comment

* fix(sdk): a replayed delete a script looks up leaves the model and serializes back hidden

* fix(sdk): shorten a comment line

* fix(sdk): a kept delete is marked hidden so the audio mixer drops it
2026-10-23 22:12:46 +00:00
Miguel Ángel e48d5d4a5c chore: release v0.8.190 (#6158) v0.8.190 2026-10-23 21:54:34 +00:00
Vance IngallsandClaude Sonnet 5.5 79b57ab12d feat: automatic tier, 240°/32 defaults, check finding, and Studio stops opting out (#6108)
* 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>
2026-10-23 14:20:17 -07:00
Miguel Ángel 73718a251b fix(engine): the BeginFrame start-up check waits for the first frame up to its deadline (#6149)
* fix(producer): a chunk with mixed capture modes retries whole in screenshot mode

* fix(producer): format the mixed capture mode change

* fix(engine): the BeginFrame start-up check waits for the first frame up to its deadline

* style(engine): format the probe tests

* fix(engine): drop a comment from the probe loop
2026-10-23 20:52:14 +00:00
Miguel Ángel 01a523bcf8 fix(engine): a render no longer decodes the videos it replaces with extracted frames (#6142)
* fix(engine): a render no longer decodes the videos it replaces with extracted frames

* fix(engine): shorten the render-frame video hold comment

* fix(engine): keep the render-frame video hold comment under the line limit

* fix(engine): hold only the videos whose render id is replaced by extracted frames

Two scenes can share a video id. The render id tells them apart, so a live video is no longer held.
2026-10-23 20:52:08 +00:00
Vance IngallsandClaude Opus 5.5 6ad7c82d21 perf(studio): an idle editor stops waking the GPU four times a second (#6109)
* perf(studio): an idle editor stops waking the GPU four times a second

The overlay frame loop's safety-net poll requested one animation frame every
250 ms for as long as the editor stayed open. Each request restarts the
display's vsync for a few frames, so a paused, untouched editor kept the GPU
process waking ~65 times a second. The poll now doubles its wait after each
idle frame up to 4 s, resets to 250 ms on any wake, and stops while the page
is hidden (visibilitychange already wakes it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(studio): keep the overlay loop's comment share at or under its base

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 20:29:32 +00:00
Vance IngallsandClaude Sonnet 5.5 4ed7813387 feat(core): capture blur falls back to per-element filters where capture cannot run (#6107)
* 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>
2026-10-23 13:46:21 -07:00
Miguel Ángel fb2f7c1a80 perf(aws-lambda): warm the audio function while the plan runs (#6148)
* perf(aws-lambda): warm the audio function while the plan runs

The audio task runs on the x86 render function, which is cold when the plan ran on the arm64 video function. A fire-and-forget invoke at the start of a render now wakes it, so the audio no longer outlasts the chunks.

* perf(aws-lambda): warm the render function only on a stack with a video function

An x86-only stack has no cold function to warm, so it takes the old path with no extra step. The CDK stack has no video function and is unchanged.

* perf(aws-lambda): wrap a template comment

* style(aws-lambda): format the warm-up template test
2026-10-23 19:32:57 +00:00
Miguel Ángel 9fe0511c2e fix(cli): render -c takes an absolute entry inside the project, relative to the project (#6141)
* fix(cli): render -c takes an absolute entry inside the project, relative to the project

An absolute entry was returned as typed, so the producer joined it onto the project path and failed with the entry not found. A project reached through a symlinked path (macOS /tmp) refused its own files as outside the project. The entry is now checked against both spellings of the project path and handed on project-relative.

* test(cli): import the filesystem helpers the entry tests use

* chore(cli): format the entry path tests
2026-10-23 19:31:30 +00:00
Miguel Ángel f8ae1483c5 fix(engine): the video frame cache budget is shared by the capture workers (#6135)
* fix(engine): the video frame cache budget is shared by the capture workers

Each capture worker built its own frame cache of up to 1500 MB of base64 strings, all on the one Node heap, then serialised every frame body for the browser. With many workers and 4K png frames the heap ran out inside JSON.stringify and the render aborted. The configured budget is now split evenly across the workers, never under 64 MB each.

* chore(engine): format and trim the frame cache budget change

* chore(engine): format the frame cache budget test

* fix(producer): chunk capture workers share the frame cache budget too

The distributed chunk path built its injectors with no options, so each worker kept the full 1500 MB cache whatever the config said. The factory now takes the budget and renderChunk passes it with the chunk worker count.

* test(producer): run the chunk injector budget test under bun and format

* fix(producer): divide the frame cache budget across workers in the producer, not the engine

The engine package was 13 bytes over its packed-size budget with the worker count threaded through it. The orchestrator and the chunk path now hand the engine each worker's share of the budget, one helper owns the division, and the engine is unchanged.
2026-10-23 19:30:53 +00:00
Miguel Ángel 28ac4d76b6 fix(studio): the thumbnail capture page loads media near the playhead like the preview (#6146) 2026-10-23 19:21:51 +00:00
James Russo 2512642d09 feat(studio): add a preview seek bar (#6103)
* feat(studio): add a preview seek bar

* fix(studio): preserve preview height with the seek bar

* fix(studio): keep the preview seek thumb fully visible
2026-10-23 18:59:59 +00:00
Miguel Ángel 1dc97921cd fix(engine): a long audio mix prepares its clips a few at a time (#6132)
* test(engine): the URL-read audio mix test stops its mix before cleanup and is not timed

* test(studio): bank the open-film thumbnail counts on the CI Chrome

The CI Chrome moved to 155 and renders one thumbnail fewer: 13 renders, 14 requests.

* fix(engine): a long audio mix prepares its clips a few at a time

A 200-clip mix started 200 ffmpeg at once and starved the host.

* chore(engine): trim the long comments in the audio mixer

Reasoning moved to the PR.

* test(engine): the mix order test starts every preparation at once

The mixer prepares clips in a core-sized pool; the test now sets enough cores.
2026-10-23 18:24:50 +00:00
Miguel Ángel b7e2278e24 chore: release v0.8.189 (#6140) v0.8.189 2026-10-23 18:13:30 +00:00
Miguel Ángel 8411956534 fix(cli): long projects check to completion: boxed resumable copies, --prepare-media, idle deadline (#6104)
* 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
2026-10-23 16:32:28 +00:00
Miguel Ángel 2b8282f321 fix(ci): a package fails its size budget only when more than 1% over it (#6131)
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.
2026-10-23 15:32:53 +00:00
Miguel Ángel bd4cae7e35 fix(engine): an opaque ProRes clip extracts to jpg frames, only ProRes with alpha stays png (#6130)
* fix(engine): an opaque ProRes clip extracts to jpg frames, only ProRes with alpha stays png

ProRes was always cached as lossless PNG in case a missed tag hid alpha. ProRes alpha shows in its pixel format (yuva444p), so the guess only costs size: a 4K 4:2:2 clip wrote about 17 MB a frame, 18 GB for 36 s. VP8 and VP9 keep PNG because their alpha rides a side channel ffprobe can miss.

* test(engine): ProRes extraction format follows the alpha plane

* chore(engine): format the ProRes extraction change
2026-10-23 14:14:06 +00:00
Miguel Ángel 132a504b69 fix(engine): encode with the same x264 and x265 thread count on any host size (#6127)
* 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
2026-10-23 14:13:57 +00:00
Miguel Ángel 2ed882ccf2 fix(studio-server): the history lock works on drives without hard links (#6125)
* fix(studio-server): the history lock works on drives without hard links

Taking the owner lock links a prepared file into place. exFAT and some network drives refuse hard links (ENOTSUP, EPERM), so the claim threw and the project history could not open. A refused link now falls back to creating the lock exclusively. A reader treats a just-created empty lock as being taken, and clears one a crashed claimant left behind after two seconds.

* test(studio-server): run the owner lock tests where node:fs can be stubbed

* fix(studio-server): the evict lock also waits out a claimant's empty file

The evict lock takes the same exclusive-create fallback as the owner lock, so it can be empty for a moment too. One deadHolder check now decides a holder is gone at all three sites: owner, evictor and the lock itself.

* chore(studio-server): trim comments on the owner lock

* test(studio-server): an empty lock is taken over once it is old, not while being written

* chore(studio-server): shorten the claim comment
2026-10-23 12:32:47 +00:00
Miguel Ángel 3cdae3804e fix(engine): mix any number of audio clips by summing them in stages (#6120)
One ffmpeg graph held every clip of a group (or of the whole film) as its own
input, so a long project ran into the open-file and input limits and the
export failed. Past 96 clips the mixer now folds slices into one running
float sum, with no cap on the total; mixes of 96 clips or fewer take the same
single-graph path as before.

A mix process now also gets a deadline that grows with the film length, and a
failed ffmpeg run keeps how long it ran and the last lines of its stderr.
2026-10-23 12:09:39 +00:00
Miguel Ángel 3badc9bfa5 chore: release v0.8.188 (#6122) v0.8.188 2026-10-23 09:53:45 +00:00
Miguel Ángel c4c1e621d4 fix(producer): a blank media src is no source instead of the page itself (#6121)
* fix(producer): a blank media src is no source instead of the page itself

A video with src="" reads back from the browser as the composition page URL. The probe then treated index.html as its media file and failed the whole render with 'media sources are text documents'. A blank own src with no <source> child now reports no source.

* style(producer): format the blank-src test
2026-10-23 09:12:40 +00:00
Miguel Ángel 401d0655fb fix(aws-lambda): keep the arm64 image staging script out of the published package (#6118)
* 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.
2026-10-23 08:52:34 +00:00
Miguel Ángel b6de25391a fix(runtime): a composition script that shadows console no longer breaks the render runtime (#6119)
A script with a top-level const console = ... hides the global from every runtime call to console.info/warn. The runtime build now maps console to globalThis.console.
2026-10-23 07:53:26 +00:00
Miguel Ángel aeecf38345 fix(producer): long renders with small frames no longer fail the up-front disk estimate (#6117)
* fix(producer): long renders with small frames no longer fail the up-front disk estimate

The pre-capture check priced PNG frames as raw RGBA, so a long, mostly transparent overlay was refused for needing hundreds of GB it would never write. The measured projection after the first frames already stops a render that cannot fit, using real frame sizes, so the estimate no longer throws.

* fix(producer): drop the comment the ratchet counts

* fix(producer): trim comments so the file's comment share does not rise
2026-10-23 07:52:35 +00:00
Miguel Ángel 69738b7ce4 fix(engine): render into a folder whose name holds a percent sign (#6116)
* fix(engine): render into a folder whose name holds a percent sign

ffmpeg reads an image sequence path as a printf template, so a folder like
"12% Up" was parsed as a format code and every video render failed. The
folder part of each frame-sequence path now goes through one helper that
doubles the percent sign.

* fix(engine): drop the helper doc line the comment ratchet rejects
2026-10-23 07:33:25 +00:00
Miguel Ángel d1aeb31bf4 fix(lint): tell an agent where to move a video nested in a timed wrapper (#6113)
* fix(lint): tell an agent where to move a video nested in a timed wrapper

The missing-start hint said to add data-start="0" even when the video sat
inside a timed wrapper, which then trips the never-both rule. The hint now
names the wrapper and its start, and following it lints clean.

* fix(lint): hint the innermost timed wrapper of a nested video

The hint used the outermost timed ancestor, so scene > clip > video told the
agent the scene timing. It now takes the innermost enclosing timed element,
counts nesting depth so a closed sibling no longer hides its parent, omits the
duration when the wrapper has none, and leaves audio on the plain hint.

* fix(lint): a closed timed sibling no longer counts as a wrapper of the next video

The nesting check compared how many closing tags sat between a timed element
and a video with how many same-name opening tags did, ignoring order, so an
unrelated later sibling cancelled the close. It now walks them in document
order and stops when the element closes.
2026-10-23 06:42:06 +00:00
Miguel Ángel 6fe83060d9 chore: release v0.8.187 (#6115) v0.8.187 2026-10-23 06:32:30 +00:00
Miguel Ángel 504ec2b307 feat(cli): lambda deploy renders on arm64 by default; --video-arch x86_64 keeps x86 (#6073)
* feat(aws-lambda): price arm64 chunk invocations at the arm64 rate

getRenderProgress takes chunkArchitecture, the architecture of the function
that renders chunks. RenderChunk invocations are priced at its per GB-second
rate, both inline and from a Distributed Map's output; plan, audio and
assemble stay at the x86 rate.

* feat(cli): render chunks on an arm64 function with lambda deploy --video-arch

--video-arch arm64 pushes the arm64 video function image to the stack's own
ECR repository and deploys the template with VideoImageUri set to its digest.
Outside a checkout the image is the one the installed adapter pins; inside a
checkout it must come from HYPERFRAMES_LAMBDA_VIDEO_IMAGE, built from that
checkout. The default stays x86_64, which always sends an empty VideoImageUri
so a redeploy drops a video function rather than keeping it.

destroy deletes the repository, render and progress price chunks at the arm64
rate, and policies lists the ECR actions.

* fix(cli): push the video image only to an ECR repository this stack tagged

* fix(cli): price the plan at the video function's rate now that it runs there

* feat(cli): lambda deploy renders on arm64 by default; --video-arch x86_64 keeps x86

* refactor(cli): drop two doc comments the names already carry
2026-10-23 05:45:49 +00:00
Vance IngallsandClaude Sonnet 5.5 e85f56e86c fix(core): capture blur strobing, backdrop box, glow drop-out and callback motion (#6106)
* 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>
2026-10-22 22:40:57 -07:00
Miguel Ángel 784e1d01d9 test(cli): process tests stop owning old processes that carry a reused parent pid on Windows (#6111)
* test(cli): process tests stop owning old processes that carry a reused parent pid on Windows

* test(cli): split the process listing parser out of the table builder
2026-10-23 04:37:09 +00:00
Miguel Ángel 0df088926e feat(aws-lambda): push a prebuilt image to ECR without Docker (#6063)
* 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).
2026-10-23 04:35:25 +00:00
Miguel Ángel 0255493913 fix(core): a scene paints no filter output from an HTML layer before its start or after its end (#6097)
* fix(core): a scene paints no filter output from an HTML layer before its start or after its end

* fix(core): a scene paints no filter output from its pseudo-element layers outside its window
2026-10-23 04:12:55 +00:00
Miguel Ángel 8d562a3d91 feat(aws-lambda): attach the arm64 video image to releases and pin it (#6070)
* feat(aws-lambda): attach the arm64 video image to releases and pin it

The release builds the arm64 video function image from the handler ZIP it
attaches, attaches it as an OCI layout tarball with its sha256, and pins it
in prebuilt/video-image.json, read through prebuiltVideoImage(). A retried
release reuses the image already attached.

* fix(aws-lambda): keep the release pin script out of the published package

pin-handler-zip.ts is run from a checkout by publish.yml; nothing in dist or the CLI imports it. Leaving it out keeps aws-lambda under its size budget with the video image reader added.
2026-10-23 03:52:35 +00:00
Miguel Ángel f6cbf87cdd feat(aws-lambda): arm64 video function image from the handler ZIP (#6064)
* 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
2026-10-23 02:41:18 +00:00
Miguel Ángel 821003b112 test(studio): retain Chrome launch diagnostics (#6077)
* test(studio): retain Chrome launch diagnostics

* fix(studio): read Chrome's version from the file on Windows instead of running it

* fix(studio): shorten the Chrome version probe
2026-10-23 01:55:52 +00:00