5670 Commits
Author SHA1 Message Date
Miguel Ángel c8288eb9b6 chore: release v0.8.172 (#5848) v0.8.172 2026-10-18 15:53:44 +00:00
Miguel Ángel 01adadb918 fix(cli): check and snapshot keep their record while another app has the history open (#5845)
* fix(cli): check and snapshot keep their record while another app has the history open

* test(cli): a held history no longer drops the snapshot record
2026-10-18 15:14:48 +00:00
Miguel Ángel ab2e939558 fix(cli): audio and timeline find media the way preview and render load it (#5846)
audio and timeline joined a clip's src to its composition's folder by hand,
so a project-root asset used from a nested scene, or a percent-encoded file
name, was "not found" although preview, check and render load it.
resolveAuthoredAssetFile now owns that lookup (the sub-composition rewrite,
then the decoded project-root resolve), and audio, timeline and
media-treatment all use it.
2026-10-18 15:01:14 +00:00
Miguel Ángel bb55640f5c fix(core): a scene paints no SVG filter grain before its start or after its end (#5828)
* fix(core): a scene paints no SVG filter grain before its start or after its end

* fix(core): a scene's filter tween starts from its stylesheet filter when frames step in

The hidden-clip rule's filter:none was still on when a tween first rendered on
the frame-sized step into its scene, so it cached none as its start value. A
seek that first renders a time now shows the hidden clips due at that time
(all of them on a jump) around the GSAP render.

* fix(core): hidden scenes block only SVG filter output, leaving HTML filters to tween

Only SVG reference filters paint without input, so the filter:none rule now
covers SVG content inside a hidden clip and nothing else. A stylesheet filter
on an HTML element stays readable whenever GSAP first renders it, including a
tween that starts a frame before its scene, and the per-step show of due clips
is no longer needed.
2026-10-18 14:57:42 +00:00
Miguel Ángel dc0e80e73a fix(cli): capture works inside an agent's macOS sandbox (#5840)
The seatbelt launch runs Chrome as one process, where there is no GPU process
to composite. Capture's flags left GPU compositing on, so every screenshot
failed and heavier pages stalled until the protocol timeout. The one-process
launch now also disables GPU compositing, as software capture already does.
2026-10-18 10:07:56 +00:00
Miguel Ángel 76ba031dcc chore: release v0.8.171 (#5838) v0.8.171 2026-10-18 09:34:30 +00:00
Miguel Ángel 834e90a779 fix(cli): check never hangs inside an agent's macOS sandbox (#5836)
Inside Codex's seatbelt the GPU probe's Metal Chrome is always killed, and
when it dies after puppeteer's setAutoAttach is answered, puppeteer waits
forever for its initial targets. The probe now answers software in a named
seatbelt without starting Chrome, and every CLI browser launch is bounded
by its timeout, so a stuck launch rejects and a signal can end the command.
2026-10-18 09:01:26 +00:00
Miguel Ángel 95a686c23d fix(cli): catalog loads registry items a few at a time so a slow link no longer times them out (#5829)
* fix(cli): catalog loads registry items a few at a time so a slow link no longer times them out

* chore(cli): keep the moved concurrency helper's doc to one line

* chore(cli): wrap the concurrency helper's doc under the line limit
2026-10-18 08:36:30 +00:00
Miguel Ángel e9181d02f5 fix(cli): check skips WebGL draws on its box-only seeks so Three.js scenes finish (#5825) 2026-10-18 07:13:31 +00:00
Miguel Ángel cadd7af2b5 fix(studio-server): send Studio's history edits to the process that owns the project's history (#5826)
* fix(studio-server): send Studio's history edits to the process that owns the project's history

* fix(studio-server): a forwarded history call gives up after 5 s, so a stopped owner cannot hang
2026-10-18 06:07:33 +00:00
Miguel Ángel 2b9009ab52 fix(studio): keep a preview reload waiting while a slow project boots (#5823)
* fix(studio): keep a preview reload waiting while a slow project boots

Studio gives a reload up to three 15 s budgets, but the player failed it at
8 s and Studio's own 5 s safety net stopped listening for the runtime. A host
can now own the load deadline (no-load-timeout); script and runtime errors
still fail at once.

* fix(player): send the 8 s load deadline through the probe's one error path
2026-10-18 05:46:53 +00:00
Miguel Ángel b74686ef8e chore: release v0.8.170 (#5822) v0.8.170 2026-10-18 05:26:26 +00:00
Miguel Ángel 7b1b0bb005 fix(studio-server): history routes say why a project's history cannot open instead of a bare 500 (#5821) 2026-10-18 05:21:15 +00:00
Miguel Ángel 49671b73dd feat(media-use): source sound effects from Openverse with the bundled library offline (#5753)
* feat(media-use): source sound effects from Openverse with the bundled library offline

* docs(media-use): name the resolve credits file without a dead path

* style(skills): restore the slideshow harness formatting the pinned formatter expects

* fix(media-use): keep Openverse credit text to one safe line and read credits without a race

* fix(media-use): Openverse sound effects record as the engine's, so a rerun writes no copy

* fix(media-use): two sound effect resolves at once each keep their credit line

* fix(media-use): the standalone media-use copy takes the same credit lock

* fix(media-use): a sound effect credit is always one safe line with a provider link

writeSfxCredit cleans the credit and source link itself, since the CLI writes
credits from a saved record. Backticks, brackets, angle brackets and backslashes
are stripped, and a source link must be https on an Openverse audio provider host
(Freesound, Jamendo, Wikimedia Commons) or it is left out.
2026-10-18 04:35:08 +00:00
Miguel Ángel deeb8d437c fix(producer): plan hash no longer depends on the scratch folder (#5782) 2026-10-18 00:34:57 +00:00
Miguel Ángel 8e4ce74118 perf(engine): long renders encode their audio in parallel segments (#5788)
* perf(engine): long renders encode their audio in parallel segments

* fix(engine): parallel audio encode counts packets from whole samples

The expected packet count came from a floating-point duration, so frame-aligned lengths such as 147.52 s (one ulp above 7080960 samples) expected one packet more than the encoder writes, the last segment check threw, and the render lost its audio. Round to whole samples first; a 147.52 s test fails without the fix.
2026-10-17 22:51:25 +00:00
Miguel Ángel aba5eae230 perf(core): every graded clip draws through one WebGL context, not one per clip (#5809) 2026-10-17 22:51:23 +00:00
Miguel Ángel 7e00f37be2 fix(cli): render starts in a sandbox that forbids lowering CPU priority (#5814) 2026-10-17 21:34:56 +00:00
Miguel Ángel d0da232ec9 fix(cli): show the real import error when a cloud adapter is installed (#5763)
lambda reported any ERR_MODULE_NOT_FOUND as '@hyperframes/aws-lambda is not installed', and cloudrun matched its package name anywhere in the message, which also names the adapter when one of its own dependencies is missing. Both now use one helper that matches only Node's "Cannot find package '<pkg>'" and rethrow anything else.
2026-10-17 18:14:02 +00:00
Miguel Ángel 20611541b1 fix(core): font lookup on a Mac no longer waits 5 s on system_profiler (#5810) 2026-10-17 18:04:15 +00:00
Miguel Ángel 1a2194c7b4 perf(producer): distributed mp4 chunks can carry their slice of the plan audio (#5803)
With the new chunksCarryAudio plan option, each mp4 chunk muxes the AAC packets of the
plan's one audio encode that start inside its frame range, so an in-storage join needs
no audio work. Today's assemble still joins such chunks into the same output.
2026-10-17 18:03:56 +00:00
Miguel Ángel cbf3f0d547 test(player): the loading-overlay debounce test no longer races real time (#5789) 2026-10-17 18:03:47 +00:00
Miguel Ángel 6ed89a1e4c fix(engine): render workers stop loading the videos whose frames come from FFmpeg (#5795)
* fix(engine): render workers stop loading the videos whose frames come from FFmpeg

* fix(engine): keep the media of a skipped video no poster sizes
2026-10-17 18:03:34 +00:00
Miguel Ángel 3b3007442a fix(core): font setup skips parsing stylesheets that cannot name a font (#5808)
* fix(core): font setup skips parsing stylesheets that cannot name a font

* fix(core): drop the comment the ratchet rejects
2026-10-17 17:59:35 +00:00
Miguel Ángel 80667726e8 perf(core): a preview loads each web font once, not once per scene (#5806)
* perf(core): a preview loads each web font once, not once per scene

* test(core): count the bundle's font rules in its styles, not in the inlined runtime
2026-10-17 17:59:27 +00:00
Miguel Ángel 7735338b49 chore: release v0.8.169 (#5804) v0.8.169 2026-10-17 16:55:51 +00:00
Miguel Ángel e2bfdf2224 fix(cli): keep check from waiting on project history another process holds (#5759)
* fix(cli): keep check from waiting on project history another process holds

* fix(cli): name the history skip instead of commenting it
2026-10-17 16:52:46 +00:00
Miguel Ángel c4440dfb20 perf(core): scenes stop animating after their clip ends, so late playback keeps its frame rate (#5741)
* perf(core): scenes stop animating after their clip ends, so late playback keeps its frame rate

A scene timeline registered on its own was re-seeked every frame up to its own
length, not its clip's end. A scene whose animation outlasts its clip kept
re-rendering while hidden; the runtime now rests it at the clip's end.

* perf(core): name the clip end instead of commenting it

* fix(core): rest only timed scenes in preview, leave render seeks as before

* fix(core): a Studio-stamped scene keeps animating after the root grows past its stamp
2026-10-17 15:59:03 +00:00
Miguel Ángel 5e7ba0be94 fix(cli): Chrome starts inside an agent's macOS sandbox as one process over a pipe (#5781) 2026-10-17 15:01:31 +00:00
Miguel Ángel b18b3e958a fix(gcp-cloud-run): the BeginFrame contract image resolves the engine's core import (#5785)
* fix(gcp-cloud-run): the BeginFrame contract image resolves the engine's core import

The engine config imports the distributed fps set from @hyperframes/core, and the contract stage of the GCP image copies only parsers and engine sources, so the import failed at build time. The set now lives in a small core subpath (@hyperframes/core/distributed-fps) that the engine imports like its other light core imports, and the contract stage copies core like parsers. The core root export is unchanged.

* style(core): format package.json
2026-10-17 11:13:06 +00:00
Miguel Ángel c715139976 test(studio): the edit accuracy bench starts one server when node is a Volta shim (#5779) 2026-10-17 10:13:28 +00:00
Miguel Ángel 6e1f9ee11f chore: release v0.8.168 (#5780) v0.8.168 2026-10-17 09:54:30 +00:00
Miguel Ángel 18a8191700 fix(cli): warn when a cloud render ignores the composition's fps (#5757)
lambda and cloudrun render at 30 fps when data-fps is outside the cloud set (for example 50 or 30000/1001) and --fps is not passed. They now say so and point at --fps. The 30 fps default has one owner, defaultCloudFps.
2026-10-17 09:52:51 +00:00
Miguel Ángel fb17f41196 perf(producer): overlap the plan step's audio work and hash files once (#5777)
* perf(producer): overlap the plan step's audio work and hash files once

* refactor(producer): simplify the plan overlap and publish path
2026-10-17 09:52:44 +00:00
Miguel Ángel 1bb0833af2 fix(cli): check's overlap pass keeps half its samples uniform and probes each overlap once (#5770) 2026-10-17 09:20:59 +00:00
Miguel Ángel 066ca7e20a fix(cli): check's overlap audit skips scenes the runtime hid (#5758) 2026-10-17 09:20:55 +00:00
Miguel Ángel 4cf0e3a0e5 fix(core): scrubbing back across a scene no longer leaves its content hidden (#5766)
* fix(core): scrubbing back across a scene no longer leaves its content hidden

* fix(core): apply a scene's same-time steps in authored order when scrubbing back onto them

* fix(core): type the steps found on a landing time
2026-10-17 09:12:58 +00:00
Miguel Ángel fc41fa277a fix(engine): local renders lay out width-only videos like the browser (#5771)
* fix(engine): local renders lay out width-only videos like the browser

* chore(engine): name the poster instead of commenting it
2026-10-17 08:45:59 +00:00
Miguel Ángel a498cf2032 perf(producer): assemble in one pass and keep its disk use under one output copy (#5768)
* perf(producer): assemble in one pass and keep its disk use under one output copy

The distributed assembler joined the chunks into a full-size file, muxed the
audio into a second one and rewrote that again for faststart, so local disk
peaked near four copies of the video and a 30-minute 1080p render came close
to Lambda's 10 GB /tmp. The join, audio mux and faststart now run as one
ffmpeg pass over the concat demuxer; the audio pad/trim takes its frame count
from the chunks. Video and audio packets are unchanged. Assemble and the
Lambda assemble handler log per-stage timings.

* refactor(producer): split assemble into plan check, cfr re-encode and final write

Keeps assemble under the complexity gate and drops an orphaned doc comment in chunkEncoder. No behaviour change.
2026-10-17 08:45:53 +00:00
Miguel Ángel c69fd2c6ec perf(producer): size distributed chunks to the worker limit and concurrency (#5765)
* perf(producer): size distributed chunks to the worker limit and concurrency

* chore(cli): drop a comment the field name already says
2026-10-17 08:45:47 +00:00
Miguel Ángel d5162beb50 refactor(core): one owner for the distributed fps set (#5756)
The integer rates Lambda and Cloud Run accept were written out by hand in
about 25 places across core, engine, producer, the adapters and the CLI.
They now come from DISTRIBUTED_FPS / DistributedFps / isDistributedFps in
@hyperframes/core. Engine config snapshots accept 25 like the rest.
2026-10-17 08:45:41 +00:00
Miguel Ángel fa93669156 fix(cli): a browser a sandbox refuses says to rerun the command with escalation (#5734) 2026-10-17 07:36:52 +00:00
Miguel Ángel caea3863b0 fix(cli): check's dense overlap pass takes at most 240 samples, at clip windows first (#5764)
* fix(cli): check's dense overlap pass takes at most 240 samples, at clip windows first

* fix(cli): a held overlap on a long film is still an error under the 240-sample bound
2026-10-17 07:23:17 +00:00
Robin EbersandCursor cc9e1bb33a feat(producer): distributed renders accept 25 fps (#5423)
Lambda and Cloud Run renders refused 25 fps although it is an integer
rate like 24, 30 and 60, and PAL footage is 25 fps. The distributed fps
set is now {24, 25, 30, 60} in the producer's config validation, the
plan v2 manifest, the render request mapping, the Lambda and Cloud Run
event types, the CLI's lambda and cloudrun commands and the regression
harnesses.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 22:00:32 -07:00
Miguel Ángel 09c0307c3f feat(producer): render at 720p and 480p from a larger composition (#5724)
## What a user can do now

Render a video at 720p or 480p: `hyperframes render --resolution 720p` (or `480p`, or the presets `landscape-720p`, `portrait-480p`, `square-720p` and so on) gives a 1280×720 or 854×480 file from a 1080p composition, and the same presets work through the studio-server render route. Before, any preset smaller than the composition was refused ("Downsampling via --resolution is not supported").

## How

- `parsers/src/types.ts`: six presets, `{landscape,portrait,square}-{720p,480p}`, and the `720p` / `480p` aliases (aspect-agnostic, like `1080p` and `4k`). 480p at 16:9 is 854×480: 480 × 16/9 is 853.33, and 4:2:0 encoders need even sides.
- `checkOutputResolutionCompatibility` now allows a preset smaller than the composition. A smaller preset may be off by under one pixel on its long side (the 854); a larger one must still match exactly and be a whole multiple, as before. The aspect remap that `--resolution 1080p` / `4k` apply on their own (`suggestMatchingPreset`, used by the CLI preflight and the compile stage) returns only a preset in the asked tier, never another: with the looser match for smaller presets, a 1922×1080 composition asked for `4k` would otherwise have been remapped to 480p. It is refused instead, as before.
- `resolveDeviceScaleFactor` returns the long side's ratio, so a smaller preset renders at a device scale factor under 1: Chrome lays the page out at composition size and rasterizes each frame straight at 1280×720 or 854×480, the way 4K already renders at 2. For 480p that is 854/1920: the width lands on 854 exactly and the height's 480.375 rounds to 480, so no pad column is added. Output sizes round as Chrome rounds the screenshot.
- The streaming encoder was given the composition's size instead of the output size on both streaming paths (`renderOrchestrator.ts`). 4K never noticed, as both sizes are even; at 480p the encoder skipped its even-size pad and libx264 refused an odd frame (`width not divisible by 2 (853x480)`). It now gets the output size, as the disk-frame encode already did.
- The paths that refused DPR > 1 (BeginFrame, drawElement, the Linux fast-capture gate) now refuse any DPR other than 1, since none of them honor a scale; scaled renders take the screenshot path, as 4K does.
- `render/outputScaleRoute.ts`: the layered compositor (HDR content, and shader transitions in webm and mov) sizes its layers and encoder from the composition and never scales, so a scaled output on that route now stops with a named error, checked where every route and replan is settled (`syncCapturePlan`, beside motion blur's route check). Before, a 1080p request from a 4K composition with shader transitions as mov would have encoded 3840×2160. HDR with `--resolution` was already refused in preflight; 4K on this route had the same gap on main and gets the same error.

## What I measured

- Real renders from source on a Linux box (`bun src/cli.ts render registry/examples/nyt-graph --resolution 720p|480p|1080p`), checked with ffprobe: 720p `h264, 1280x720, yuv420p, 15.0 s`; 480p `h264, 854x480, yuv420p, 15.0 s`; 1080p unchanged at 1920x1080. A frame of each, looked at: same chart and layout, crisp text, and the 480p right-edge column is the page's own color (250,248,242), not a pad.
- Before the encoder-size fix the 480p render failed with `[libx264] width not divisible by 2 (853x480)`; after it, it renders.
- `outputScaleRoute.test.ts`: the real route choice for a 4K composition with shader transitions as mov (`outputSupportsPageSideShaderCompositing`, `shouldUseLayeredComposite`, `createCapturePlan`, `resolveDeviceScaleFactor`) lands on `hdr_layered` at scale 0.5 and is refused. CLI preflight and the compile stage both refuse `4k` on a 1922×1080 composition.
- fails without the fix: without the same-tier rule, the preflight and compile tests for 1922×1080 at `4k` pass the render through at 854×480; without the route check, the mov test does not throw.
- At d33c4458: producer `outputScaleRoute` + `compileStage` 18/18 three runs in a row, `shared` (bun) 9/9, cli `render` 104/104 once and its new test three runs in a row; tsc clean in producer and cli. Earlier at e72f6507: parsers `outputResolutionCompatibility` 20/20, core `index` 26/26, producer `resolveDeviceScaleFactor` 14/14, cli `render` + `parseOutputResolution` 122/122, studio-server `render` 53/53. The new cases fail on main (the presets don't exist; smaller presets are refused). tsc clean in parsers, engine, producer and cli; oxlint and oxfmt clean.

## What I did NOT exercise

- Distributed (Lambda / Cloud Run) renders at 720p/480p: the chunk worker's sizes round the same way, but I did not run a distributed render.
- Studio's render queue keeps its own copy of the rule (`RenderQueue.tsx` `scaleApplies`) and offers no 720p/480p option; unchanged here.
2026-10-16 20:58:15 -07:00
Miguel Ángel e6ce733796 fix(lint): stop flagging dir on <html>; a render test now guards RTL export (#5744) 2026-10-17 03:25:12 +00:00
Miguel Ángel 40c64255e8 fix(skills): regenerate manifest after HyperFrames guidance update (#5747) 2026-10-16 19:57:12 -07:00
kevin 6dba67a0c3 fix(cli): honour HYPERFRAMES_SKIP_SKILLS in skills update and bare skills (#5743)
skills update (bare or named) and bare skills now exit 0 with a one-line
notice instead of installing or pruning when HYPERFRAMES_SKIP_SKILLS=1, so a
pinned skills install survives the router's trigger-time update. Closes #5695.
2026-10-17 02:34:40 +00:00
Miguel Ángel eab6ca15b1 fix(cli): timeline lists clips nested in a timed element and lint flags ones that never show (#5732)
* fix(cli): timeline lists clips nested in a timed element and lint flags ones that never show

* docs(lint): wrap the nested-clip entries

* docs(skills): format the nested-clip note and refresh the skills manifest

* fix(cli): keep timeline edits from colliding with a clip's own wrapper

* fix(cli): cut clips nested in a wrapper at their sub-composition's in-point

* refactor(lint): split the nested-clip visibility checks to keep the audit green

* style(cli): restore the timeline test's formatting
2026-10-17 02:27:17 +00:00
Miguel Ángel 4e6ee6d230 fix(engine): a WebGPU block mounted as a sub-composition gets WebGPU like a root one (#5735)
compositionRequiresWebGpu read only the first data-composition-id tag, the
page root. In bundled HTML a mounted composition's root keeps
data-requires-webgpu but carries data-hf-inner-root instead of a composition
id, so a pasted-in WebGPU block launched without WebGPU and failed with
'WebGPU unavailable' even with PRODUCER_ALLOW_SOFTWARE_WEBGPU set.
2026-10-17 00:52:55 +00:00