Commit Graph
6396 Commits
Author SHA1 Message Date
Miguel Ángel cdc86117c7 fix(engine): a render from an app that bundles the engine stamps the engine's version (#6686)
* fix(engine): a render from an app that bundles the engine stamps the engine's version

* test(engine): the bundled-layout fixture names a generic app
2026-10-28 17:51:43 +00:00
Miguel Ángel 710314c15e fix(studio): an undo or redo counts as a preview change from its key, not from its reload (#6683)
* fix(studio): an undo or redo counts as a preview change from its key, not from its reload

* refactor(studio): count a history step from its key with the page flag on noteIdle
2026-10-28 17:51:22 +00:00
Miguel Ángel 067cbf7acf fix(producer): a capture that landed every frame is not failed for the work after its last frame (#6674)
* fix(producer): a capture that landed every frame is not failed for the work after its last frame

* chore(producer): drop a comment the ratchet counts
2026-10-28 17:48:50 +00:00
Miguel Ángel c2cde2fb62 perf(producer): the audio true-peak check seeks to each segment instead of decoding up to it (#6672)
* perf(producer): the audio true-peak check seeks to each segment instead of decoding up to it

Each parallel segment scan decoded the AAC from the start and trimmed after, so the last one decoded the whole file. It now seeks to a whole second one second before its segment and trims relative to it: the same peak, measured in about half the time.

* test(producer): the true-peak check's ffmpeg tests run in the integration lane
2026-10-28 17:48:36 +00:00
Miguel Ángel 203a08dd7c fix(cli): check no longer treats a transparent canvas as covering the frame (#6681)
* fix(cli): check no longer treats a transparent canvas as covering the frame

* fix(cli): a canvas with an opaque CSS background still counts as covering
2026-10-28 17:48:26 +00:00
Vance IngallsandClaude Opus 5.5 c569760a7a fix(core): grade groups resolve per composition; freeze frames keep their lane (#6685)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 10:51:17 -07:00
Vance Ingalls 4719543e97 chore: release v0.8.214 (#6684) v0.8.214 2026-10-28 10:17:04 -07:00
Vance IngallsandClaude Opus 5.5 348971c222 fix(studio): a tab's save keeps the newer video version another tab recorded (#6682)
* fix(studio): a tab's save keeps the newer video version another tab recorded

The thumbnail index merge let this tab's ETags win for every source, including
ones it only read at startup, so another tab's move to a new version was undone
by this tab's next save: the next session validated the old ETag, missed the
disk and decoded once more. Now another tab's ETag wins except for sources this
tab recorded a new version of since its last save.

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

* fix(studio): the ETag merge says whose moves it keeps in its names, not comments

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

* test(studio): the stored-thumbnail reset forgets this tab's drops and moves too

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 10:10:56 -07:00
Vance IngallsandClaude Opus 5.5 09b1290474 feat(core): grade groups grade a whole lane from one hf-color-group (#6678)
An inert <hf-color-group> holds a lane's data-color-grading; <video>, <img>
and <canvas> join with data-color-group. A member's own grading overrides
its group's. The runtime discovers members, regrades them when the group's
grading or a member's data-color-group changes, and ungrades them when the
group element or its grading goes, drawing only members on screen.

Render keeps group-graded video at full size like directly graded video,
and lint accepts (and still validates) grading on hf-color-group.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 10:10:42 -07:00
Vance IngallsandClaude Opus 5.5 c9017f45f4 feat(core): bake a colour grade into a 3D LUT (.cube) (#6679)
bakeHfColorGradingCube evaluates the runtime shader's colour path on the
CPU (primary, tonal wheels, RGB and hue curves, secondaries, output LUT,
intensity mix) through the same 8-bit texture quantisation, and writes a
red-fastest .cube for ffmpeg's lut3d. Grades with spatial stages (grain,
vignette, effects) throw, as does a grade whose LUT was not passed in.

A Chromium test draws a 4,096-colour lattice through the real runtime
and compares every tile with the bake.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 16:45:15 +00:00
Miguel Ángel c6af88d2e6 chore: release v0.8.213 (#6673) v0.8.213 2026-10-28 16:40:44 +00:00
Vance IngallsandClaude Opus 5.5 e38585ef5f perf(studio): timeline thumbnails come back from disk in the next session (#6676)
* perf(studio): timeline thumbnails come back from disk in the next session

Reopening a project re-read every video's index and re-decoded every strip:
the frame cache and the source facts lived in memory, keyed by the session.

Now each source's facts (aspect, duration) and every decoded frame are kept
in Cache Storage under the file's ETag, the version the server says it is. A
new session checks each source once with a conditional one-byte read: a 304
means the stored frames are still that file's, and a strip they cover paints
without opening a reader. A new ETag drops what was stored for the old one.
Sources are dropped whole, least recently used first, past 256 MiB. A store
that is missing or fails (private window, quota) warns once and the strip
decodes as before.

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

* fix(studio): a reopened project checks its videos again, and the disk index counts a frame once

The stored version a session validated was kept per video for the whole page,
so reopening a project after one of its videos was replaced at the same path
painted the old file's frames until a full reload. It is now kept per video
and project open (its contentVersion): each open re-validates once.

The index now counts a frame stored again under its name once, and merges
with what other tabs saved before writing, keeping caches this tab dropped
out, so the 256 MiB budget holds across tabs.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 16:39:02 +00:00
Miguel Ángel 67c7ec27d9 fix(core): bundled fonts in a linked stylesheet or a font shorthand are seen by the bundler (#6665)
* fix(core): a font declared in a linked stylesheet no longer trips the bundler's font warning

* fix(core): drop the guard's explanatory comment

* fix(core): bundled fonts named only in a CSS font shorthand load

* fix(core): a font shorthand loads faces but never fails the cloud system-font check

* test(producer): regenerate static-volume-future-set for the font shorthand face

* test(producer): regenerate two more fixtures for the font shorthand face
2026-10-28 14:08:07 +00:00
Miguel Ángel 5b5633fbbc fix(cli): website capture ends with what it has instead of running until it is killed (#6662)
* fix(cli): website capture stops inlining slow images and stylesheets and says what it skipped

* fix(cli): a capture ends at four minutes with its partial bundle unless told otherwise

* fix(cli): a capture's deadline starts once its browser is up, and the kept-image count is exact

* fix(cli): an image whose fetch the deadline cut off always counts as kept
2026-10-28 13:56:48 +00:00
Miguel Ángel cf627a8619 fix(core): a preview media load that fails is reported, not swallowed (#6666)
* fix(core): a preview media load that fails is reported, not swallowed

* fix(core): an audio fed by source children reports its failed load; the shape lives in the protocol

* fix(core): the audio load watcher finds elements from any realm

* build(core): regenerate the inline position-edits render script
2026-10-28 13:49:30 +00:00
Miguel Ángel cd3b90d1ed fix(cli): snapshot decodes each video once for all the times it shows (#6667)
* fix(cli): snapshot decodes each video once for all the times it shows

* fix(cli): shorten a comment line
2026-10-28 13:48:44 +00:00
Miguel Ángel a1733f4e40 fix(cli): check treats text inside a closed details element as not rendered (#6669) 2026-10-28 13:41:27 +00:00
Miguel Ángel 8fe0ce660b fix(cli): check probes each audio file once, however many clips are cut from it (#6654)
* fix(cli): check probes each audio file once, however many clips are cut from it

* fix(cli): drop an empty stray test file from the audio probe change
2026-10-28 13:39:17 +00:00
Miguel Ángel 40c28e10db fix(studio): a timeline edit on a clip the file lacks says how the clip was looked up (#6661)
* fix(studio): a timeline edit on a clip the file lacks says how the clip was looked up

* fix(studio): name only the lookups a missing clip was really found by

* refactor(studio): split the missing-clip lookup words into small helpers

* refactor(studio): fit the missing-clip lookup words under the no-visible-change size
2026-10-28 13:30:46 +00:00
Miguel Ángel 6486d58021 test(producer): stall test no longer waits a real second for the progress flush (#6658) 2026-10-28 13:30:36 +00:00
Miguel Ángel 4db1214e7d test(engine): the multi-chunk encode test gets the 30 s the other ffmpeg tests have (#6663)
* test(engine): the multi-chunk encode test gets the 30 s the other ffmpeg tests have

* test(engine): the mixed-path concat test gets the same 30 s
2026-10-28 13:13:18 +00:00
Miguel Ángel 5a9fdd524b fix(cli): a late teardown error no longer turns a finished command into exit 1 (#6659)
* fix(cli): a late teardown error no longer turns a finished command into exit 1

* test(cli): keep only the live invariant in the late-rejection test comment

* test(cli): shorten the late-rejection test comment
2026-10-28 13:13:07 +00:00
Miguel Ángel 437784038b fix(engine): late-starting MP4 and WebM videos render every frame the preview shows (#6664)
* fix(engine): late-starting MP4 and WebM videos render every frame the preview shows

The browser counts an MP4 or WebM from timestamp 0, but FFmpeg seeks from the file's
earliest stream start. A file whose first frame comes after 0 (a fragment cut from a
longer stream, an edit-list offset, a WebM with a start offset) was extracted early and
short, so renders tripped the video coverage gate. Every video seek now maps media time
to FFmpeg's timeline, and the video begins where the browser shows it.

* fix(engine): audio from late-starting MP4 and WebM plays when the preview plays it

The mixer seeked and trimmed audio by FFmpeg's clock, which starts at the file's earliest
stream. In an MP4 or WebM whose streams all start after 0, the browser plays that audio
later, so the render heard it early by the same amount. The mixer now maps media time onto
FFmpeg's clock with the same offset frame extraction uses.

* fix(engine): a late-starting file's picture shows on the clock its audio plays on

* test(producer): vfr-screen-recording baseline shows each frame at its timestamp
2026-10-28 13:09:53 +00:00
Xuanru LiandJerrai 1df31d8717 fix(audio): a sound is voice only with a voice-range pitch, judged on the whole clip (#6653)
* fix(audio): a sound is voice only with a voice-range pitch, judged on the whole clip

Two ways the kind guess called a non-voice sound voice:

- A short hit, click or whoosh can have a speech-shaped envelope. classify() now also
  measures the share of 40 ms frames with a 70-500 Hz fundamental (YIN, moved from the
  CLI into core) and calls a sound voice only when that share reaches 0.15. Speech
  measured 0.22-0.43; impacts, clicks and whooshes 0.
- A range query judged a clip's kind on the slice it covered, so 1.7 s of a music bed
  read as voice. A film clip's kind is now judged on all of the clip that plays.

Even-paced speech (a 64 s TTS take) moves less at syllable rate and was called music;
with no beat and a voiced share of 0.25 or more it is now voice.

* chore(audio): keep each touched file's comment share at or under its base

The Comments check failed on run.ts, targets.ts and audioAnalysis.ts because the
PR added comment lines to files whose share may only go down. Drops the added
doc comments; no code change.

— Jerrai

* fix(audio): preserve clip evidence and stereo pitch during analysis

* fix(audio): bound and cache whole-clip kind analysis

* fix(audio): retain native-rate kind evidence when available

---------

Co-authored-by: Jerrai <jerrai@heygen.com>
2026-10-28 12:48:08 +00:00
Miguel Ángel eba596d026 chore: release v0.8.212 (#6660) v0.8.212 2026-10-28 12:29:35 +00:00
James RussoandClaude Opus 5.5 2d3033c719 fix(cli): exit 2 for remaining flag validation errors (#6639)
* fix(cli): exit 2 for remaining flag validation errors

Flag and argument checks that commands make themselves still exited 1 and
reported runtime_error under --json. They now take the existing usage-error
path (failUsage / CliUsageError): exit 2 and a usage_error envelope. This
covers lambda and cloudrun render/render-batch (--width/--height, --fps,
positive-number, enum and --output-resolution flags, unknown subcommands),
capture, feedback, benchmark --runs, tts, init, snapshot, compare,
grade-compare, check, normalize-audio, media-treatment, play/present --port,
models, docs, browser and telemetry unknown subcommands, cloud render
conflicting inputs, cloud delete without --no-confirm, and inline --variables.
A few checks now run before project or stack lookup so a bad flag is reported
first. Runtime failures (missing files, network, render errors) still exit 1.

An unattended spinner now writes its start and progress text as plain stderr
lines, not only the outcome, so tools watching init can see "Transcoding".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): reject a bad snapshot --frames, quiet spinner progress, drop added comments

- `snapshot --frames abc` (or 0, -3, 2.5) is now a usage error: exit 2, and a
  usage_error envelope under --json. It used to fall back to 5 frames silently.
- An unattended spinner prints its start line once and its outcome, but no
  longer one line per progress update, which flooded stderr during
  remove-background and the Chrome download.
- Removes the comment lines this branch added, so the comment ratchet passes.
- The render --json test now expects exit 2 and usage_error for bad inline
  --variables.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-28 11:27:49 +00:00
Miguel Ángel b84762230f fix(cli): snapshot warns when a video on screen has no source to read a frame from (#6656) 2026-10-28 11:14:19 +00:00
James RussoandClaude Opus 5.5 85efaece7e fix(cli): every --json failure answers with one envelope, and --json only where supported (#6622)
* fix(cli): every --json failure answers with one envelope, and --json only where supported

Under --json, failures came back as a bare string (`"error": "Command failed"`
from lint), a command-specific shape, or nothing at all on stdout (info,
compositions, render, snapshot, cloud get). Now every failure is
`{"ok": false, "error": {"code", "message", "hint"?}}`:

- jsonFailure() in utils/jsonError.ts builds it. The commands that printed
  `{ok: false, error: "<text>"}` use it, keeping their extra fields (lint's
  findings, validate's errors). A refused timeline edit now writes it to stdout
  instead of stderr, and keeps its `reason` and `fix` fields.
- When a --json run fails without writing anything to stdout, cli.ts writes the
  envelope itself: usage errors carry their own code (unknown_flag,
  unknown_command, json_unsupported, confirmation_required), anything else is
  runtime_error with the message the command printed on stderr.

--json is no longer accepted by every command. A command supports it by
declaring a `json` arg; the others reject it with exit 2 and the code
json_unsupported, instead of accepting the flag and printing prose.

The contract test now checks every command's --json path, and packages/cli
gets an AGENTS.md with the rules: stdout is data, stderr is messages, never
prompt without a TTY, exit 2 for usage errors, --json always returns an
envelope.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): put the real failure in the --json envelope, never the generic text or a notice

lint --json and check --json in a directory with no project answered
"message": "Command failed". The project check reports the problem, then
throws a presented "Command failed" with the real error as its cause, and the
command's own catch serialized that generic text. failureMessage() now returns
the cause, or else what the command reported through errorBox, and every
command catch that writes a --json failure uses it (lint, check, validate,
layout, snapshot, compare, grade-compare, media-treatment, tts,
remove-background, add, skills, lambda policies). compare, grade-compare and
add share failCaught() for that catch.

The boundary envelope follows the same order: the error's own message unless
it is the generic one, then its cause, then the errorBox title and hint, and
only then the stderr text. Notices (the telemetry disclosure, deprecation
lines, diag progress) are no longer captured, so validate, layout and inspect
no longer put "is deprecated" in the error message.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): render --json keeps human output off stdout so a failure answers with the envelope

Under --json a single render printed its header, lint findings and progress on
stdout, so a later failure such as `render --variables '{bad' --json` ended
with no JSON envelope. --json now silences that output the way --batch --json
already did, and --variables is parsed before the browser check so a bad value
fails without a Chrome download.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): a --strict lint abort says why, even under --json

Under --json the render is quiet, so a --strict lint abort printed nothing and
the envelope said only "Command failed". The abort is now an error box on
stderr in every mode, naming the error and warning counts and the first
finding, and the --json envelope carries the same message. Lint now runs
before the browser check, so the abort never waits on a Chrome download.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(cli): time the render setup cancel from the wrapper's exit

The 1 s budget started when the command did. With lint now running before the
browser check, its subprocess (about 250 ms here) sat inside that budget, and
the test failed once under load. The budget now starts when the wrapper exits,
which is the latency the test is about.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-28 10:42:10 +00:00
Miguel Ángel b6eaed7cae fix(cli): a check page forces a WebGL drawing buffer but keeps no list of 2d canvases (#6650) 2026-10-28 10:41:22 +00:00
Xuanru Li 205c1a31d3 fix(inspector): live queries see every composition, wherever the playhead is (#6651)
* fix(inspector): live queries see every composition, wherever the playhead is

The Studio preview parks scenes away from the playhead, so a live inspect, timeline or
element-map query answered missing for every element in a parked composition. Each live
query now holds every scene mounted under its own holder, then hands the scene window back.
__hfHoldScenes keeps holds per holder, so the inspector letting go never drops a scene
Studio holds.

* fix(inspector): isolate concurrent live query scene holds
2026-10-28 10:39:49 +00:00
Miguel Ángel ecad35a225 fix(cli): resume a model download that stalls partway instead of failing (#6649)
* fix(cli): resume a model download that stalls partway instead of failing

* fix(cli): pin the idle-only timeout; one-line doc for the resume

* fix(cli): resume from the bytes on disk and check the size before caching

* refactor(cli): split the download response handling into named steps
2026-10-28 10:27:00 +00:00
Miguel Ángel 7112e4b6bf test(studio): wait for the first undo press before the edit that should block the second (#6652) 2026-10-28 10:19:07 +00:00
Vance IngallsandClaude Opus 5.5 a88751d9d6 fix(studio): a thumbnail reader forgets a start's failures after a minute (#6642)
* fix(studio): a thumbnail reader forgets a start's failures after a minute

A pooled thumbnail reader lives for as long as clips keep asking it, and each start
byte has a retry budget of two. That budget never reset, so two transient failures at
one start early in a session made a third, twenty minutes later after the cache evicted
it, give up at once: the reader retired and the clip showed an error.

A start's failures now count for a minute. One bout of retries (8 s stalls, 0.5 s and
1 s waits) fits inside it, so a start that keeps failing still gives up; one met again
much later starts afresh. Clearing the count when a read from that start delivers bytes
would not do: a server that ignores Range sends bytes 0 onward again before it breaks at
the same byte, and would retry forever.

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

* fix(studio): the start-failure window says what it is in its name, not a comment

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

* fix(studio): a start's retry budget resets when a read from it ends cleanly, not after a minute

The one-minute window only held while each attempt took under a minute. An attempt has
no limit before its answer, and a body that keeps sending never stalls, so a server that
ignores Range, sends slowly and breaks past a minute re-downloaded from 0 forever.

A start's failures are now forgotten when a read from it ends cleanly: its body finishes,
or mediabunny cancels it at its target. A server that breaks before the target never ends
a read cleanly, so its budget still runs out, however slowly it fails.

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

* fix(studio): a read mediabunny stops at its target ends cleanly and resets its start's budget

mediabunny's UrlSource never cancels the body: once a read reaches its target, _runWorker
aborts the signal it passed in, and the pending pull fails with an AbortError. So the reset
on cancel never fired, and any open bytes=N- read stopped at its target kept its start's
failures.

The request now listens on its own signal, which follows mediabunny's and is unlinked once
the read ends: an abort while live counts as a clean end only when mediabunny's signal is
the one aborted, never a stall or a dispose.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 10:15:28 +00:00
Miguel Ángel 0e32ee9b56 ci: keep the bun cache warm and skip binary downloads in preflight (#6648)
* ci: keep the bun cache warm and skip binary downloads in preflight

* test(ci): pin the single cache writer and preflight's skipped downloads

* test(ci): keep the cache-writer scan out of the test body
2026-10-28 09:52:07 +00:00
Miguel Ángel 5a0179b88e fix(studio-server): the render route passes a MOV ProRes profile to the render (#6644)
* fix(studio-server): the render route passes a MOV ProRes profile to the render

* fix(studio-server): drop the added comment line so the comment share does not rise
2026-10-28 09:44:32 +00:00
Miguel Ángel 7758d9b8fd fix(engine): a Windows browser that will not start says where and why (#6647)
* fix(cli): a Windows browser that will not start says where and why, not just spawn UNKNOWN

* fix(engine): the launch diagnosis names a browser file Windows refuses to run

* fix(engine): the launch diagnosis names a browser file Windows refuses to run (engine part)
2026-10-28 09:41:17 +00:00
James RussoandClaude Opus 5.5 e2c9151320 fix(cli): add help [cmd], suggest the closest command or flag, and quiet the nags (#6621)
* fix(cli): add help [cmd], suggest the closest command or flag, and quiet the nags

`hyperframes help` printed "Unknown command help" with raw ANSI codes and no
suggestion. It is now an alias: `help [cmd...]` is `[cmd...] --help`.

An unknown command (at the root or inside a group like `cloud`) prints
"Unknown command: rendr", "Did you mean 'hyperframes render'?" and the group's
usage, all on stderr, and exits 2. An unknown flag suggests the flag it most
likely means ("Did you mean --output?"). Usage text printed for a usage error
now goes to stderr instead of stdout, and citty's colors are stripped from
help whenever this CLI's own colors are off.

No update, skills or app-history notice is appended to a usage error. The
skills notice shows at most once a day per project (timestamps in
~/.hyperframes/skills-notice.json) and, in an unattended run, only when a core
skill is missing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): reject an unknown top-level command without loading every command

citty looks for an alias on every registered command before it reports an
unknown name, which loads all of them. From a source checkout without the
studio packages built, that meant `hyperframes rendr` failed in preview's
build check with exit 1 instead of suggesting `render`. The name is now
checked against the registry first.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(cli): help with a mistyped subcommand reports it like the command itself

`hyperframes help cloud rendr` printed cloud's help and exited 0, while
`hyperframes cloud rendr` exits 2 with a suggestion. Both now exit 2 with the
suggestion. A group that also takes positional arguments (timeline takes a
project directory) still shows its help for a word it does not know.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-28 09:39:06 +00:00
Miguel Ángel 3d1c0764a0 fix(cli): check prints a progress line as each pass after overlap begins (#6646)
* fix(cli): check prints a progress line as each pass after overlap begins

* fix(cli): check counts finished passes and drops the comment the ratchet flagged
2026-10-28 09:37:17 +00:00
Miguel Ángel 5ed694264e chore: release v0.8.211 (#6623) v0.8.211 2026-10-28 09:30:08 +00:00
Miguel Ángel 6553b1f6ba perf(producer): --gpu renders encode while frames are captured (#6631) 2026-10-28 09:24:27 +00:00
Vance IngallsandClaude Opus 5.5 fa137cdfef fix(studio): a slow thumbnail job keeps a reader that other clips still decode from (#6643)
* fix(studio): a slow thumbnail job keeps a reader that other clips still decode from

A job that timed out judged the reader by its own progress only. A poster job that got
an already-open reader while a sibling was decoding on it, and took more than its
budget to reach its one frame (a slow link, Chrome's six connections held), logged a
timeout and retired a healthy reader, so the next clip re-read the index.

The pooled reader now counts the frames every job decodes from it, and a timed-out job
retires it only when no frame came from it while the job held it.

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

* fix(studio): the reader's frame count says whose frames it counts in its name, not a comment

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:18:55 +00:00
Miguel Ángel 30df5a48af fix(studio-server): undo and redo on Windows restore a file something holds open (#6628)
* fix(studio-server): undo and redo on Windows restore a file something holds open

* fix(studio-server): a restore waits for the preview's reads to close before replacing the file

* fix(studio-server): a restore checks the project folder after the preview's reads close
2026-10-28 09:12:54 +00:00
Miguel Ángel 51f9fa7c54 perf(aws-lambda): small-chunk cloud renders stitch their output in parallel (#6635)
* perf(aws-lambda): small-chunk cloud renders stitch their output in parallel

When every chunk's payload is under S3's 5 MiB part minimum (typical for short 4K chunks), the assemble step could not copy any part server-side and pulled the whole output through one Lambda's network link. Those range-only parts now go to parallel invocations of the warm function that just rendered the chunks, about 32 MiB each and never more at once than the stack's reserved concurrency allows. The output bytes are unchanged.

* fix(aws-lambda): a throttled stitch helper builds its parts in the assemble instead

Another render can hold the stack's reserved concurrency while this one assembles, so a helper invocation may be refused. The assemble now builds that batch itself, logs it, and counts it in assemble_stages; the helper's error type is kept as the error name. Comments trimmed back to each file's share at the base.

* chore(aws-lambda): raise the package size budget to the measured size plus headroom

The parallel stitch helpers add about 1.7 KB packed and 9.9 KB unpacked; the new budget is the measured size times the checker's own 1.05 headroom.
2026-10-28 09:03:42 +00:00
Miguel Ángel 8e63faf846 perf(engine): a --gpu render probes the GPU encoders once, not twice (#6638)
* perf(engine,cli): a machine's GPU encoder probe runs once, not on every render

* refactor(engine): one shared probe per process, no answer kept on disk
2026-10-28 08:44:28 +00:00
Miguel Ángel e6476c2cd5 perf(engine): Macs with a hardware GPU render with more capture workers (#6636)
* perf(engine): Macs with a hardware GPU render with more capture workers

* perf(engine): Apple-GPU worker sizing skips layered renders; fewer comment lines

* test(producer): format
2026-10-28 08:42:31 +00:00
Miguel Ángel 2ddf41eb7d perf(producer): download a remote video once per render (#6637) 2026-10-28 08:04:05 +00:00
James RussoandClaude Opus 5.5 0bdc128cb5 fix(cli): never prompt or auto-install when nobody is at the terminal (#6617)
* fix(cli): never prompt or auto-install when nobody is at the terminal

publish and upgrade called the confirmation prompt with no terminal check.
With stdin from /dev/null, publish exited 13; with an open pipe it hung.
Unattended and without --yes, both now exit 2 before doing any work, and
stderr names --yes (upgrade also names --check).

All prompts and spinners now go through ui/prompts.ts, the only module allowed
to import @clack/prompts (a lint rule and a contract test enforce it). Its
confirm, select, text and password refuse to prompt in an unattended run, and
its spinner prints only the final line, on stderr. init, feedback, catalog and
cloud delete decide whether to ask with isUnattended() instead of a TTY check
of their own.

The background auto-install no longer starts in an unattended run, and the
update notice ("Run: npm install -g ...") is not shown while an install for
that version is pending. CI now counts as set for any value but "false" or "0".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(cli): neutralize agent detection in the catalog offer tests through isAgentDriven

Attendance now reads isAgentDriven(), so that is what the catalog tests stub to
stay independent of the agent running the suite.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-28 07:56:20 +00:00
Miguel Ángel 6cf4b56697 fix(engine): a render on Windows opens no console windows (#6613)
* ci(windows): probe that a render opens no visible window

* ci(windows): the probe names what opened each window

* ci(windows): the probe spawns each binary alone, with and without detached

* ci(windows): the probe logs the processes started during the render

* ci(windows): the probe also spawns Chrome one consoleless hop down

* ci(windows): the probe logs each child started without windowsHide

* ci(windows): the probe logs every child process with its windowsHide and detached

* fix(engine): Chrome keeps the CLI's console on Windows, so a render opens no window

* fix(cli): every child the CLI starts on Windows is hidden unless it opts out

* ci(windows): the probe bisects Chrome's flags and keeps each command's output

* ci(windows): the probe names the Chrome process behind each window

* ci(windows): the probe loads a page in Chrome directly and one hidden hop down

* ci(windows): the probe prints the Chrome main command line and renders with the reporter's flags

* ci(windows): the probe narrows the CLI's Chrome arguments to the ones that open a window

* ci(windows): the probe launches the CLI's Chrome arguments one hidden hop down

* ci(windows): the probe launches Chrome through the engine's browser pool

* ci(windows): the probe varies what the engine passes to Puppeteer

* fix(engine): renders on Windows launch Chrome with a Puppeteer that keeps it attached

* refactor(cli): hideChildConsoles copies its arguments and keeps one comment line
2026-10-28 07:48:40 +00:00
Miguel Ángel 1d97ed24c6 perf(producer): the audio mix runs while capture starts its browsers (#6632)
* perf(producer): the audio mix runs while capture starts its browsers

* test(producer): master-bus test stops the render at capture setup
2026-10-28 07:36:32 +00:00
Miguel Ángel 3cc296845a feat(core): name why a choppy preview window dropped frames (#6634)
* feat(core): name why a choppy preview window dropped frames

The playback reading now says how much of the window was frames skipped
and how much was a playing video waiting for data, and carries the p50
and p95 gap between the runtime's ticks, so a report can tell a slow
compositor from a stalled decoder.

* chore(core): drop comments the ratchet counts
2026-10-28 07:25:00 +00:00