Commit Graph
5372 Commits
Author SHA1 Message Date
Abdullah M. Nazif c9ddbd2d42 fix(cli): report snapshot contact sheet failures (#5196) 2026-10-08 20:08:53 +00:00
Abdullah M. Nazif 85360f370a fix(cli): explain hardware GPU screenshot fallback (#5200) 2026-10-08 20:07:01 +00:00
Abdullah M. Nazif 037f731280 fix(cli): close unused download responses before following redirects (#5261) 2026-10-08 20:06:42 +00:00
Abdullah M. Nazif 730a330800 fix(engine): invalidate cached media metadata after file changes (#5258) 2026-10-08 20:04:48 +00:00
Abdullah M. Nazif 58b7a4662d fix(cli): serve empty project assets without a stream error (#5234) 2026-10-08 20:03:36 +00:00
Abdullah M. Nazif b407178f3a fix(cli): validate cached versions in update metadata (#5214) 2026-10-08 20:02:44 +00:00
Abdullah M. Nazif 548426fef4 fix(sdk): decode scoped IDs when replaying patches (#5243) 2026-10-08 20:01:20 +00:00
Abdullah M. Nazif 95d5494baa fix(engine): preserve out-of-range HDR object positions (#5266) 2026-10-08 20:00:37 +00:00
Abdullah M. Nazif 971e1373bd fix(engine): restore discarded warmup timing samples (#5204) 2026-10-08 20:00:27 +00:00
Abdullah M. Nazif ea033bcd5a fix(sdk): preserve fragments with inert document markers (#5219) 2026-10-08 19:58:54 +00:00
Abdullah M. Nazif 8efd9ca523 fix(sdk): drain pending saves and adapter storage on flush (#5285) 2026-10-08 19:51:25 +00:00
Abdullah M. Nazif 29b901dc60 fix(skills): preserve preset color roles in frame contrast checks (#5195) 2026-10-08 19:32:24 +00:00
Abdullah M. Nazif 84bbeee4f3 docs(contributing): explain signature and workflow approval requirements (#5197) 2026-10-08 19:31:57 +00:00
Abdullah M. Nazif 710b7f5bcc docs(skills): preserve glyphs in text reveal masks (#5241) 2026-10-08 19:31:56 +00:00
Abdullah M. Nazif e529fbb015 docs: explain negative z-index stacking contexts (#5201) 2026-10-08 19:31:12 +00:00
Abdullah M. Nazif bb05bbe146 fix(core): test the minimum font size when fitting text (#5283) 2026-10-08 19:30:17 +00:00
Abdullah M. Nazif f478fa6ff5 fix(core): read music sources through the shared HTML scanner (#5281) 2026-10-08 19:30:10 +00:00
James RussoandClaude Opus 5.5 de1ef5a05a fix(engine): extract long video ranges in segments and keep plan uploads flowing (#5280)
* fix(engine): extract long video ranges in segments and keep plan uploads flowing

A distributed render's plan step extracted a whole video in one ffmpeg process,
so a long clip hit ffmpegProcessTimeout (300 s) however much of Lambda's
15-minute limit was left, and every frame then uploaded in batches of 16 that
each waited for their slowest blob.

Ranges longer than two minutes now extract as two-minute segments that run side
by side, each numbered after the last. They sample the same absolute times as a
single pass, so the frames are byte-identical (checked on a 26-minute, 37,440
frame clip). Plan v2 publishing keeps 32 uploads in flight instead of batching.
On a 10 GB Lambda the 26-minute clip's plan step fell from 395 s to 200 s.

The Lambda docs now state the plan-step length bound.

Fixes #5260

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

* fix(engine): split extraction on an integer frame count

The split compared and subtracted float seconds, so 200.3 - 80.3 became
120.00000000000001 s: a second segment got `-t 1.42e-14`, which ffmpeg
rejects, failing an ordinary two-minute range. The frame count now comes from
extractionFrameCountForDuration, the rule a single pass follows, and every
segment takes an exact frame count.

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-08 19:07:46 +00:00
James RussoandClaude Opus 5.5 e07b64101b test(aws-lambda): compare every CDK Lambda payload against the SAM template (#5282)
* test(aws-lambda): compare every CDK Lambda payload against the SAM template

#5259 shipped because CDK drops null-valued payload fields when it renders the
state machine, so the construct's source and its deployed definition disagreed
with nothing to notice. This test reads all six Lambda task payloads from the
synthesized definition and requires each to equal the SAM template's, reading
`States.StringToJson('null')` as the null it sends. A field dropped or renamed
on either side now fails CI, not just AssembleV2's audio.

Refs #5259

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

* test(aws-lambda): discover Lambda tasks for the payload parity check

The parity test took its tasks from helpers that name six states, so a seventh
Lambda task on either side would be skipped and the check would stay green.
It now walks both definitions, Map and Parallel processors included, collects
every lambda:invoke task by name, requires the two name sets to match, then
compares each payload.

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

* test(aws-lambda): key discovered Lambda tasks by their nested state path

Keying tasks by state name let two branches that each hold a RenderChunk
collapse into one entry, so the other's payload went unchecked. Tasks are now
keyed by path (a Map processor as `Map/`, a Parallel branch as `Parallel/<i>/`),
and a regression test keeps two same-named branch tasks apart.

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-08 18:58:33 +00:00
Abdullah M. Nazif 006f87d1f0 fix(aws-lambda): preserve null audio in CDK v2 assembly (#5262) 2026-10-08 18:15:48 +00:00
Miguel Ángel 621ddd4693 fix(studio): stop file names with %, apostrophes or $ from breaking Studio (#5156)
* fix(studio): serve files whose name has a bare percent sign

A browser sends src="100%.png" with the % unescaped, and decoding the whole path threw, so Studio answered 500. Only well-formed escapes are decoded now.

* fix(cli): serve files with a bare percent sign from the check and snapshot server

One shared decode rule for request paths; the image thumbnail route's malformed-escape 400 could no longer fire, so a name that is not a file is a 404.

* fix(cli): name a failed file with a bare percent sign in check

* fix(studio): keep apostrophes and dollar signs in file names when editing attributes

An attribute value is matched by its own quote kind, so src="it's.png" is replaced instead of duplicated, and every insert uses a function replacer, so $1 or $& in a file name is written as is.

* fix(studio): rename references spelled with %20 or &amp; and read quoted media sources

WIP: not yet tested; see the resume note.

* fix(studio): match whole attribute names when editing media sources

* fix(studio): compare decoded names and escape replacement references

* fix(studio): preserve file references across URL and string consumers

* docs: clarify raw file references and URL fields

* fix(studio): keep raw path identity distinct from URL spelling

* fix: share attribute grammar and decode media URLs at their readers

* fix: preserve local filename identity in HTML URL output

* fix: decode entity references before rename candidate matching

* style: format rename entity handling and regressions

* fix: preserve platform file identity when renaming references

* fix(core): decode CSS escapes before rebasing asset URLs

* fix(core): consume CSS continuations before URL encoding

* fix(core): preserve CSS URL identity and escape rebased suffixes

* fix(core): express CSS suffix controls with Unicode categories

* fix(core): preserve URL backslash identity while rebasing

* test(core): respect platform backslash filename semantics

* fix(studio): a dropped file with %, # or ? in its name loads, and an encoded reference renames

* fix(studio): read an authored src as a URL so dropped files with spaces, %, # or ? load

* fix(studio): media routes find files named with ? or #, and the probe reads a src as a URL

* fix(studio-server): one rule for request file paths, so loudness finds files named with %, # or ?

* chore: drop the design note from the change

* fix(core): rename replaced clips whose src is URL-encoded
2026-10-08 16:37:49 +00:00
Miguel Ángel 0b21016905 chore: release v0.8.142 (#5257) v0.8.142 2026-10-08 16:34:18 +00:00
Kenny 4d61c9ff2f fix(studio): delete renders after job cache expiry (#4825) 2026-10-08 09:04:07 -07:00
Miguel Ángel c7d41dee5a fix(cli): enforce capture screenshot limits and report interruptions (#5171)
* fix(cli): enforce capture screenshot limits and report interruptions

* refactor(cli): split screenshot step out of capture animation phase

Keeps the screenshot limit outside the mirrored core-extraction argument
list and moves scroll screenshots into their own step, so both stay under
the audit's duplication and complexity gates with no behaviour change.
2026-10-08 15:41:50 +00:00
Miguel Ángel 8dae5ef89b fix(core): make grading writes previewable and honor computed properties (#5157)
* fix(core): write only authored hue curves, so a single hue curve passes lint

* fix(cli): apply over a grading saved with empty hue curves instead of refusing it

* fix(core): read grading properties from computed style, and stop advising an inline start

* fix(cli): preserve validation during legacy hue repair

* fix(cli): preview grading writes without apply
2026-10-08 15:21:47 +00:00
Monster-GM 202d5b5300 fix(engine): tag GPU H.265 streams as hvc1 (#5029) 2026-10-08 15:02:33 +00:00
Miguel Ángel 188475aaf2 fix(cli): group sampled check findings by element and rule (#5174)
* fix(cli): group sampled check findings by element and rule

* fix(cli): preserve unique elements when grouping audit samples

* fix(cli): retain geometry sample times until report grouping

* fix(cli): keep grouped finding evidence time paired with geometry
2026-10-08 10:38:45 +00:00
Miguel Ángel de4b31a262 fix(studio): update unreferenced generated media names on replace (#5169)
* fix(core): derive generated media labels from current source

* fix(core): decode generated media filenames

* fix(core): share filename decoding across URL forms

* fix(core): strip filename suffixes without unsafe indexing

* fix(studio): rename unreferenced generated media ids on replace

* fix(core): register media ID exports and trim names in linear time
2026-10-08 10:38:12 +00:00
Miguel Ángel cf1b04bb71 feat(cli): accept host-managed HeyGen access tokens (#5177)
* fix(auth): accept host-managed HeyGen access tokens

* fix(auth): share environment header validation

* docs(auth): condense credential resolver guidance

* fix(auth): report the account a browser login just signed in

Identify the account from the tokens the login issued, not a fresh credential
lookup that an environment token or API key outranks.

* docs(auth): list the host access token in the CLI credential order

* fix(cli): name a rejected host access token on publish

Publish told a host-token user to log in again, which cannot help while the token outranks the login.

* docs(auth): show the media credential order main resolves

* fix(cli): warn at logout that a host access token still signs commands

One map of environment credential variables, owned by the resolver, now feeds the logout warning and both publish messages.

* docs(auth): scope the media credential order to the audio scripts

* refactor(cli): read every env credential name from the resolver's map
2026-10-08 10:33:57 +00:00
Miguel Ángel 042ec2ebd0 fix(studio): restore overscan and publish the zoom viewport (#5151)
* fix(studio): restore the timeline overscan budget

* test(studio): align window witnesses with the restored budget

* fix(studio): publish the viewport when a zoom anchor lands

* fix(studio): publish the viewport when a zoom-out ends at its laid-out scale

An eased zoom-out that ends at the scale already laid out only moves the scroll position,
so the timeline drew from the old position until the next scroll event. The zoom input
now publishes that scroll through the same callback the zoom anchor uses.

* fix(studio): a pan at the laid-out scale publishes its scroll before paint

The same-scale ending of a zoom ran outside React, so its scroll publish rendered on the
next task, after the browser painted the old window. It now flushes like the scale change
beside it, and its test pans at the laid-out scale instead of changing it.

* test(studio): a same-scale pan must render its scroll before paint

Reads the render window outside act, so unwrapping the flush fails it, and records each
published scroll at call time instead of reading the live element afterwards.
2026-10-08 08:37:55 +00:00
Xuanru LiandClaude Opus 5.5 4a335aa6a1 chore: release v0.8.141 (#5193)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
v0.8.141
2026-10-08 05:56:58 +00:00
Jerrai 0c76e52446 feat(fonts): record resolver stage diagnostics on fail-closed font errors (#5189)
* feat(core): per-compile font fetch diagnostics on the fail-closed Unresolved error

Attach a stage record to the per-compile fail-closed Unresolved FontFetchError
so callers can log why a family did not resolve. The record is an ordered list
of families, each with required/resolved flags and its fetch attempts (path,
authored/default URL, text param, CSS cache fresh/hit, real CSS HTTP status,
block and regex-match counts, asset totals and non-ok HTTP statuses). It holds
enums, counts and HTTP statuses only: no family names, URLs, CSS or page text.
The error class, name, code, message, familyName and unresolvedFamilies are
unchanged, and shared rejected errors are never mutated.

- Jerrai

* test(fonts): diagnostics for the authored-stylesheet fallback; fix the cssStatus comment

Add generic tests (invented family names) for an authored stylesheet that is rejected or
yields zero faces and falls back to the default request, an authored success with no fallback
attempt, an unchanged transient UNAVAILABLE, and a failed case that records both attempts with
the error contract unchanged. cssStatus doc now reads: no HTTP response was recorded for that
attempt.

- Jerrai

* test(fonts): pin cache labels of a repeated fallback compile and the Unresolved error's url and cause

A second compile of the same failing authored-plus-default page must label both attempts as
cache hits while the first compile's are fresh. The Unresolved error keeps url "" and no
cause, as at main.

- Jerrai

* fix(fonts): label a CSS lookup's cache hit before awaiting it

An optional family can join an in-flight CSS lookup that then rejects as UNAVAILABLE. The
rejection is swallowed, but the attempt kept its default cssCache "fresh" because the label was
written only after the await. The label is now recorded on the caller's own attempt before the
await. The shared result and error objects are not touched; retry, fallback and selection are
unchanged.

- Jerrai

* test(fonts): make the concurrent-join synchronization explicit

The second compile now passes its own AbortSignal; a lookup that awaits a shared promise
registers a listener on it, so the test waits (bounded) until the joiner has reached the optional
family's lookup and asserts the optional URL still has exactly one request before releasing the
deferred 503. The first compile's optional attempt must be fresh and the joiner's a hit, in that
order.

- Jerrai
2026-10-08 02:50:31 +00:00
Miguel Ángel b66e8a195e perf(studio): preview decodes hard-to-play videos at the size they are shown (#5184)
* perf(studio): preview decodes hard-to-play videos at the size they are shown

Studio's copy of an HEVC or ProRes clip was made at the source's full size, so a 4K clip was decoded at 4K inside a preview a few hundred pixels wide, and back-to-back 4K clips flashed the background at their cuts. The player now reports how large it shows the composition, the runtime asks for a copy sized to the shown video (on a ladder of sizes, never above the source), and Studio stops pre-warming source-size copies. CLI play, static serving, publish and renders keep the source-size copy.

* fix(studio): sized preview copies follow zoom, hover previews and the newest size

Studio reports the preview's on-screen scale when a zoom settles and for composition hover previews, which no player measures. The runtime loads only the newest size asked for, regrows a first copy made while the frame grew, and never replaces a copy with one smaller on either side. The player's scale is measured against the frame's own layout width.

* fix(studio): keep comment shares and file sizes within the repo ratchets

* test(studio-server): end the sizing test's ffprobe options before its input

* fix(studio): hover previews report their size once their runtime is ready
2026-10-08 00:26:08 +00:00
Xuanru LiandClaude Opus 5.5 09f359c33e fix(core): embed the weight-list font when the page's Google link gives none (#5186)
* fix(core): embed the weight-list font when the page's Google link gives none

Since #4569 the renderer fetches the Google Fonts URL the page wrote. When
that link is malformed (e.g. Bricolage Grotesque with wght@100..900, past the
family's 200..800), Google answers 400, the family stays unresolved, and a
distributed render fails closed. Before #4569 the renderer's own weight-list
request resolved it.

If the page's links yield no usable faces, fall back to that weight-list
request and warn with each link's status. Fail-closed transient errors still
throw before the fallback, and a working link is used unchanged. Preview and
render share this resolver, so both embed the same faces.

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

* test(core): cover a multi-family Google link that silently drops a family

Production pages link Bricolage Grotesque with opsz@7..96 alongside a second
family; Google answers 200 and returns only the second family.

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

* fix(core): log the font-link fallback as fields, without the link URL

An authored link can carry page text in `text=`, so the warning now records
only the family, each link's outcome (http_<status> or no_faces) and how many
faces the weight-list fallback embedded.

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

* test(core): import the font resolver at module scope in the Google subset tests

Also drops the two comments the fallback added, so the file's comment share does not rise.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 00:05:31 +00:00
Wenbo ZhuandClaude Opus 5.5 21d14b25b3 feat(media-use): reach HeyGen through a host app's gateway (HEYGEN_API_BASE) (#5181)
* feat(media-use): reach HeyGen through a host app's gateway (HEYGEN_API_BASE)

- The audio engine sends every HeyGen call to $HEYGEN_API_BASE when set, as the heygen CLI does; plain HTTP needs
  $HEYGEN_ALLOW_HTTP=1, the CLI's own rule. A host gateway (that base with its own key) wins over a host OAuth token.
- The skill says: inside a host that gives HeyGen access, no CLI install or sign-in, prefer the host's own HeyGen
  tools, relay a refused call's message as written and do not switch providers on your own.

HyperFrames Desktop sets these variables when a HeyGen API key is saved in its Settings, so media-use's TTS, catalog
search and avatar calls are paid by that key.

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

* chore(media-use): format the edited tables and regenerate the skills manifest

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

* fix(media-use): host access applies only when HEYGEN_API_BASE is set; HeyGen credentials stay on HeyGen hosts

- The "use the host's own tools first / no CLI sign-in" guidance is now conditional on HEYGEN_API_BASE alone, so an
  agent outside such a host (Claude Code with a HeyGen MCP connector, say) keeps today's media-use journey. The
  compressed copies in README, CLAUDE.md, the prompting overview, general-video and the capability menu are back to
  main's wording.
- A base outside heygen.com gets no stored or host OAuth credential, only a key set for it (review on #5181).

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

* fix(media-use): a project's .env cannot set the HeyGen base or allow HTTP

loadEnvFromDir copied HEYGEN_API_BASE from a project file into process.env, and the host gateway path then sent the
person's shell HEYGEN_API_KEY to that host. A host app sets HEYGEN_API_BASE and HEYGEN_ALLOW_HTTP in the environment
it spawns, never in a project file, so the .env loader skips both.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 18:30:56 +00:00
Miguel Ángel d2a3c076d0 fix(player): load compositions from document duration (#5176)
* docs(player): map composition probe readiness contract

* fix(player): resolve preview readiness from document duration

* fix(player): preserve timeline grace and terminal probe failure

* fix(player): scope probe rejection to the failed document

* test(player): share terminal readiness fixtures
2026-10-07 18:08:20 +00:00
Miguel Ángel 5c7f6316d3 chore: release v0.8.140 (#5175) v0.8.140 2026-10-07 10:04:56 +00:00
Miguel Ángel 1e711b087d feat(studio): a host can show the hover box and give the stage its starting size (#5116)
* test(studio): pin a host's hover box and a starting stage size

* feat(studio): a host can show the hover box and give the stage its starting size

* fix(studio): keep the comment share flat and let a measured size win over the hint
2026-10-07 09:13:07 +00:00
Miguel Ángel 17c699db4a fix(studio): replacing an image's fill on an img changes its picture (#5170)
* fix(studio): replace image fill through its source attribute

* fix(studio): preserve literal percent image sources
2026-10-07 09:11:58 +00:00
Miguel Ángel 9701f0da61 fix(core): keep rebound root timelines clock-driven (#5165)
* fix(core): keep rebound root timelines clock-driven

* test(core): verify transport advances the rebound root
2026-10-07 08:44:52 +00:00
Miguel Ángel da6259896b fix(core): settle slow documents against their own frame cadence (#5168)
* docs(core): map document-relative readiness contract

* fix(core): measure idle against document frame gaps

* fix(core): preserve fast-page readiness while calibrating slow frames

* docs(core): clarify readiness coverage and measurement provenance
2026-10-07 08:40:57 +00:00
Miguel Ángel 87387268a7 fix(core): preserve fresh host-driven timeline updates (#5159)
* perf(core): render a playing frame once when the host also ticks it

* fix(core): reset host tick dedup on playback start

* fix(core): deduplicate host seeks by rendered timeline time

* test(core): advance host tick clocks deterministically
2026-10-07 08:01:27 +00:00
Miguel Ángel e21ab020da fix(cli): preserve transcribed words before speech onset (#5164) 2026-10-07 07:46:58 +00:00
Miguel Ángel 9f09861d2e perf(studio): zooming a long timeline re-decodes and redraws less (#5142)
* perf(studio): reuse filmstrip frames when a zoom changes a strip's width

A zoom that changed a strip's frame count decoded the whole strip again
on the main thread, and showed the clip's single poster frame until it
landed. Frames are now sampled at each slice's left edge, so a strip
twice as long holds every frame of the shorter one; decoded frames are
shared per source and time, so zooming out decodes nothing and zooming in
decodes only the new half. The strip on screen stays until the new width
is ready.

* perf(studio): redraw only the waveforms on or near the screen after a zoom

A zoom resizes every mounted clip, and every mounted waveform redrew in
that same frame, on screen or not. A waveform now draws only while it is
on or near the screen (one shared observer, a screen of margin each side)
and draws when it comes near if a zoom changed it meanwhile.

* perf(studio): never start a beat-analysis stage while the timeline zooms

Analyzing a long music track runs for over a second on the main thread,
and when it started late it landed inside a zoom. The analysis now awaits
an optional pause before each long stage; Studio's waits for the timeline
to rest and the browser to go idle. Results are unchanged. The tempo
detector's stage is one call, so a zoom that starts inside it still waits
for it.

* fix(studio): reuse a filmstrip frame only where its own strip would show it

A shared frame was keyed by the time a strip asked for, not the time it
was decoded at, so a coarse strip's keyframe-early frame could land in a
finer strip and run its frames backwards. A frame now carries its decoded
time and is reused only when the strip would have accepted that keyframe
itself. The frame-count ladder now tops out at a power of two (32), so
its top step shares frames with the one below instead of none.

* feat(studio): show the clip's last frame at the end of every filmstrip

Left-edge sampling never showed a clip's final slice, so an end card or a
fade-out was missing from its strip. Each strip now also decodes the
clip's last frame (at its own time, never an earlier keyframe), shared
across zoom steps like the others, and the last tile shows it.

The strip on screen is now held per clip and range, not per props: a
clip crossing between on and off screen keeps it, and a clip narrowed to
one frame lets it go, so widening again never re-decodes a dropped width.

* docs(studio): keep the filmstrip files at or under main's comment share

The comment ratchet allows no file a higher share of comment lines than on
main; the added explanations move to the PR description.

* fix(studio): keep a held filmstrip leased so a full cache cannot evict it mid-zoom

The hold leased the strip on screen only once a new width was requested,
so for one commit it had no lease; with the cache over budget it was
evicted, the poster showed, and the old width decoded again. The strip on
screen is now leased for as long as it is the clip's, and shown only while
held. A frame that fails to decode takes the frame before it, so every
slot keeps its time and the last tile still shows the end.

* refactor(studio): one owner for which clip a held filmstrip belongs to

The hold checked the clip twice, in its lease and again before showing
it; the lease alone decides now, and the cross-clip test pins it. A test
now also pins that a filled frame is not charged to the cache.

* fix(core): hold the tempo detector until idle after a slow import

* fix(studio): scope shared video frames to timeline sessions

* chore(core): keep beat analysis comments within the ratchet
2026-10-07 07:38:13 +00:00
Miguel Ángel dbb8222c8d fix(studio): catch up the preview after missed file changes (#5126)
* fix(studio): recover previews after missed file changes

* fix(studio): keep queued reloads with the current owner

* fix(studio): keep a queued outside change when switching compositions

* test(cli): the project watcher closes its parent folder's watch too

* fix(studio): keep the project-folder change out of the shared signature check

* fix(studio): keep the edited file's draft and reload every session after a reconnect

A reconnect or replaced folder is a project-wide change: every open SDK session reloads, a failed save is recorded against the file the draft belongs to, and two queued changes to different files merge into one project-wide reload.

* fix(studio): refresh the preview cache when the .hyperframes folder is replaced

* fix(cli): report the files of a folder moved into the project

Replacing a populated folder such as .hyperframes/ now reaches the signature cache through its files, so the shared signature rule stays as on main and Studio's own writes inside .hyperframes/ still invalidate nothing.

* refactor(studio): name the merged file-change payload and share the test sender

* fix(studio): keep a failed draft's recovery copy until that file itself saves

* style(studio): rewrap the merged payload line after the rename

* fix(studio): deliver a held change's full reload when its block clears

A clean save, Use external and Keep Studio now all deliver the merged scope a blocked change owes, and a moved-in .hyperframes folder reports its manifests so a dropped one still clears the cached signature.

* fix(studio): keep restored drafts out of the reload scope and clear the lint gates

* fix(studio): retain held reload scope before React renders
2026-10-07 07:32:16 +00:00
Miguel Ángel 4fcad1ed74 fix(media-use): use a host app's music tools; fail loudly without the heygen CLI (#5154)
* fix(media-use): use a host app's music tools; fail loudly without the heygen CLI

* fix(media-use): name the heygen CLI only for music and sound effects; keep the sfx test offline

* fix(skills): workflow skills use a host app's own music tools first

* fix(skills): say how a host app's music track reaches the narrated workflows

* fix(skills): set a host app's music track after the last audio pass

* fix(skills): a host-music film with no narration still fetches its sound effects

* fix(media-use): keep the stub-failure test off the machine's heygen CLI; sync the skills manifest

* fix(skills): audio passes keep a host app's music and sounds; silent needs no sound cues

* fix(skills): host audio keeps string frames, stops on a broken audio_meta.json, survives a re-run

* fix(skills): report a host audio entry with no path and a broken sidecar instead of crashing

* fix(skills): keep host audio written while an audio pass runs

* test(cli): serve the telemetry capture while the resolve child waits on it

* test(cli): fail the telemetry capture when the resolve child exits non-zero

* style(skills): wrap the host audio comment to the 120-column limit
2026-10-07 07:16:51 +00:00
Miguel Ángel 80c2547eac feat(cli): send missing features and host-app friction as ratingless feedback (#5140)
* docs(skills): ask agents inside a host app to report its friction as feedback

* feat(cli): send missing features and host-app friction as ratingless feedback

* fix(cli): keep the rating error, refuse blank reports and document ratingless feedback

* docs(skills): leave names and figures out of missing-feature reports

* docs(skills): keep anything private out of missing-feature reports
2026-10-07 06:40:37 +00:00
Miguel Ángel 9e0d05d450 fix(engine): auto worker count stays within the heap budget (#5149)
* fix(engine): automatic worker count never exceeds what the V8 heap can feed

A 24 GB Mac exporting a 43 s portrait project with 22 videos picked 5 capture workers against a 4192 MB heap that feeds about 4, and the render died at 62% with JavaScript heap out of memory. The heap budget was only advisory; it now caps the automatic count (bound 'heap'). An explicit --workers N is untouched.

* test(engine): heap-cap test restores its mocks even when an assertion fails

Review findings on #5149: the os and v8 mocks were restored only if every assertion passed, and two comments still called the heap cap advisory.

* docs(engine): heap budget comments say it caps auto sizing
2026-10-07 06:11:30 +00:00
Miguel Ángel 9fbec22596 fix(cli): load telemetry without the core and engine barrels, and stop the history test racing (#5153)
* fix(cli): load telemetry without the core and engine barrels, and stop the history test racing

* fix(cli): load history from its leaf subpath, and fail the history test fast on a crashed CLI
2026-10-07 06:05:41 +00:00
Miguel Ángel 3b89bfe8a7 fix(engine): motion blur works over video by holding each video frame across samples (#5150)
* fix(engine): motion blur works over video by holding each video frame across samples

Each output frame's injected video frame is now held for every sub-frame
sample and probe of that frame, so footage stays as shot and only the
layers over it blur. The refusal for sessions with a before-capture hook
is removed.

Fixes #5144

* fix(engine): trim held-video comments, pin the new test-classification hash

* fix(engine): a blurred frame at a sub-composition cut keeps its video

Each sample now decides which videos show at its own time, like every other
layer, and only a video on screen at the output frame's time keeps that
frame's picture. Before, the visible set was taken at the frame's time while
sub-composition hosts were hidden at the sample's time, so the samples just
before a cut showed no video and the cut frame dipped toward black. The
before-capture hook takes the held time as an optional third argument, and
the early injection is gone: the GPU re-render now always runs at the page's
own time.

* style(engine): one doc comment for the before-capture hook

* fix(engine): a video animating its own style stays in step when blurred

The injector copies a video's own style only when its frame changes, so the
first injection of each blurred frame now runs at the frame's own time
before the samples. A video sliding by its own transform no longer sits a
quarter frame behind with blur on.

* test(producer): a two-line header for the motion blur over video test

* test(producer): reach the cut fixture by path, not a URL fallow reads as an import
2026-10-07 05:46:10 +00:00