* 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>
* 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>
* 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 & 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
* 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.
* 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
* 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
* 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
* 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
* 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.
* 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