Commit Graph
6215 Commits
Author SHA1 Message Date
Vance IngallsandClaude Sonnet 5.5 1c463a2fec fix(core): refuse nested vfx capture canvases and scope host lookups (#4804)
A `<canvas layoutsubtree class="hf-vfx-src">` nested inside another capture
canvas blocks the renderer main thread in drawElementImage, so `check` and
`snapshot` time out with no output. Refuse either direction of the nest with a
reportVfxError and leave the chain unregistered.

Also scope the two host lookups to immediate children (`:scope >`) so a nested
host's src/out canvas is never bound to the outer host, and read the
`data-vfx-ref-visible` flag in resolveCaptureSource so a canvas reached as both
a host's own source and as a ref gets one consistent `visible`/keepBitmap.

Refs #4405

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 01:30:58 +00:00
Poetry Of CodeandClaude Opus 5.5 9ac16b4aa1 fix: install git hooks in linked worktrees (#4387) (#4722)
The prepare guard used `test -d .git`, which is false in a linked
worktree where .git is a file, so lefthook install was silently skipped.
Use `test -e .git` so clones and linked worktrees both install hooks
while non-git directories remain a no-op.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 01:30:56 +00:00
Dante 7dd83b8513 test: preserve browser cleanup and regression failure evidence (#5773) 2026-10-24 01:30:54 +00:00
Abdullah M. Nazif 5b8e539f96 fix(cli): recognize painted ancestor text backgrounds (#5342)
* fix(cli): recognize painted gradient children on older Chrome

* fix(cli): recognize ancestor image text masks

* fix(cli): report clipped ancestor text on older Chrome
2026-10-24 00:43:33 +00:00
jbernard077andClaude Fable 5.1 e6fba28b66 fix(registry): centre mk-callout-highlight with flex instead of a script-time measurement (#5118)
* fix(registry): centre mk-callout-highlight with flex instead of a script-time measurement

The text box becomes a flex item of a full-frame wrapper (#mk-ch-center),
so its position is laid out by the browser rather than read from a
getBoundingClientRect() taken at script time. That measurement depends on
which font faces are loaded when the script runs; the fonts gate covers only
faces that static markup already uses, so script-built text measures
against the fallback face (#5112). A non-null CONFIG.x / CONFIG.y becomes a
margin offset on that axis. The GSAP y entrance stays on the box, which
carries no CSS transform.

Also drops the scene-exit visibility tail (the runtime hides the clip at
its window end; the 0.02 s tail never reached a frame at 30 fps) and
removes data-layout-allow-occlusion from #mk-ch-box: nothing beneath the
block is text, and the attribute made the contrast audit skip the block's
own words whenever a later full-frame host answered the hit-test probe.

Renders are bit-identical to the measured version when the face is loaded
(nine-scene project at v0.8.135, 1380/1380 frames). CONFIG and the sweep
mechanic are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(registry): keep only the flex centring in mk-callout-highlight

Address review: restore the scene-exit visibility tail and the layout
occlusion attribute, which belong to family-wide conventions and are out
of scope here, and note the x + maxWidth clip edge in CONFIG.

Refs #5112

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-24 00:43:31 +00:00
yi111andYi-111-a 0648f1ed8d fix(lint): recognise .avif and .m4a in the media src kind rule (#4779)
The media_src_kind_mismatch rule classified .mp3 and .jpeg but not .m4a or
.avif, so an <img> pointed at an audio file or a <video> at a still slipped
through for those two formats. Add .avif to IMAGE_SRC_EXT and .m4a to
AUDIO_SRC_EXT.

.ogg stays out: its container can carry Theora video as well as Vorbis or
Opus, so the extension does not name the kind. .m4a is the opposite case --
it is audio-only, and video in the same MP4 container is .m4v, which
VIDEO_SRC_EXT already lists. The .ogg case keeps its existing coverage and
gains an explicit unknown-extension case alongside it.

Refs #4382

Co-authored-by: Yi-111-a <Yi-111-a@users.noreply.github.com>
2026-10-24 00:43:28 +00:00
Abdullah M. Nazif 0e0f7f803a fix(cli): audit audio slots at their authored playback speed (#5300) 2026-10-24 00:43:25 +00:00
Akshar Patel 3fdb544c45 fix(studio): preserve authored caption groups on import (#3900) 2026-10-24 00:43:22 +00:00
Abdullah M. Nazif d25b18a007 fix(producer): close artifact frame validation gaps (#5242)
* fix(producer): close artifact frame validation gaps

* docs(producer): describe quiet artifact metadata validation
2026-10-24 00:43:17 +00:00
Abdullah M. Nazif aac0e7df51 fix(producer): make sparse keyframe advice conditional (#5343)
* fix(producer): make sparse keyframe advice conditional

* fix(producer): clarify sparse keyframes do not affect rendering
2026-10-24 00:43:14 +00:00
Abdullah M. Nazif 051584c222 fix(cli): hide background preview consoles (#5345)
* fix(cli): request hidden consoles for background launches

* fix(cli): keep custom browser windows visible
2026-10-24 00:43:12 +00:00
Jerrai 7a34d6fbe4 docs: align render, fps and transcribe docs with the CLI (#6166)
* docs: align render, fps and transcribe docs with the CLI

- docs/guides/rendering.mdx: default render quality is `looks` (CRF 16),
  not `standard`.
- docs/packages/cli.mdx, render --help (render.ts): default --output is the
  timestamped `renders/<project>_<YYYY-MM-DD>_<HH-MM-SS>.<ext>`.
- skills/hyperframes-cli/references/preview-render.md: local --fps accepts
  1-240 or a rational and defaults to the root data-fps, else 30; only
  lambda and cloudrun are limited to 24, 30, 60.
- skills/hyperframes-cli/references/lint-validate-inspect.md: snapshot
  --range is documented as START,END like every other reference.
- skills/media-use/references/audio.md, skills/media-use/audio/references/
  transcribe.md, skills/hyperframes/references/captions.md: name the entry
  point for each transcription default (transcribe.mjs prefers Parakeet;
  `hyperframes transcribe` uses Parakeet when installed, else the small.en
  whisper model).
- transcribe --model help text and docs/packages/cli.mdx: list `small` and
  `medium`, which the CLI already accepts.
- skills-manifest.json: regenerated hashes for the three skills touched.

— Jerrai

* docs: lambda and cloud run also accept 25 fps

Distributed renders accept 24, 25, 30 and 60 fps (DISTRIBUTED_FPS in
packages/core/src/distributedFps.ts).

- skills/hyperframes-cli/references/preview-render.md: the --fps note
  lists 24, 25, 30, 60 for lambda and cloudrun.
- skills/hyperframes-cli/references/lambda.md, cloudrun.md: --fps lists
  include 25.
- packages/cli/src/docs/rendering.md (`hyperframes docs rendering`): --fps
  said 24, 30 or 60 and --quality defaulted to standard; local render takes
  1-240 or a rational, default root data-fps else 30, and the default
  quality is looks.
- skills-manifest.json: regenerated hyperframes-cli hash.

— Jerrai

* chore(skills): regenerate skills manifest after formatting

The format hook rewrote skill markdown after the manifest hook had hashed
it, leaving the hyperframes-cli hash stale.

— Jerrai
2026-10-24 00:29:56 +00:00
yi111 3fa94b2156 test(lint): cover three media rules that had no tests (#5402)
duplicate_media_discovery_risk, self_closing_media_tag and
placeholder_media_url were defined in media.ts but no test in
media.test.ts mentioned any of their codes.

Adds a trigger case per rule (asserting code and severity) plus
negative cases that pin the boundary each rule actually keys on:
one fingerprint field differing for duplicate discovery, an
explicitly closed tag, and a real src.

Each new test was checked to fail against a disabled rule.
2026-10-24 00:28:17 +00:00
Dante 828adc1ce7 fix(engine): await seek completion during drawElement batches (#5430) 2026-10-24 00:28:13 +00:00
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