* test(studio): score edits on GSAP-animated size, scale, rotation, keyframes and from/fromTo
* test(studio): settle the edit bench only once no shadow preview is waiting to be swapped in
* test(studio): end a drag at its recorded pointer-up, even when no frame painted while held
* ci(studio): run the keyframed edit accuracy cases in CI across 20 shards
* refactor(studio): split the keyframe render and stray-css checks into small helpers
* test(studio): bank the edit bench's smooth verdict as smooth, judged on the worst frame's work
* style(studio): name the edit gate's smooth count helper
* test(studio): count smooth edit cases only among the accurate ones
* test(studio): only keyframed cases wait for the preview swap, so others keep main's undo timing
* refactor(studio): keep the edit bench's keyframe check apart from the hidden preview scan
* test(studio): check the keyframed cases are in the full grid without pinning its size
* test(studio): quarantine the nudge sequence whose redo waits behind preview videos (#4889)
* test(studio): bank the 255 keyframed edit cases that pass
* fix(studio): Renders dropdown options take a mouse click again
* test(studio): name a Select whose list never opens and keep both Studio layout checks running
* fix(studio): a text edited in place shows its caret in a scaled preview
* fix(studio): the drawn caret follows a resized preview and stands right at bold words and empty lines
* fix(studio-server): a write receipt's replaced version comes only from the bytes it overwrote
* fix(studio): the drawn caret stands on the selection inside a transformed canvas
* test(studio): unmount the caret tests' roots before clearing the page
* test(studio): the caret gate waits for each key to land and retries a text that does not open
* test(studio): the caret gate checks what the DOM can tell at a soft wrap, and shows a text that would not open
* test(studio): the caret gate opens a text with a double press when click and Enter do not
* fix(studio): the drawn caret follows a pan or zoom of the preview
* fix(studio): the inline text style bar stands by the selected text inside a transformed canvas
* fix(studio): after End at a soft wrap the drawn caret stays at the line's end, as the system caret does
* fix(studio): only End keeps the drawn caret at a wrapped line's end, as Chrome's own caret
* refactor(studio): share the test mount helper and split the caret's placement for the code gates
* docs(studio): trim the drawn caret's comments to the package's share
* fix(studio): open inline edit on styled text whose nodes come from another window
* refactor(studio): name the realm-safe html check instead of commenting it
* docs(studio): shorten the caret fixture comment to the 120-character limit
* ci(studio): measure drag smoothness on a quiet runner, with a blank-page control
* ci(studio): gates read the base the run measured, not the base branch tip
* style(ci): format the base-file test
* fix(studio): the timeline scroll gate catches dropped frames in the virtualized view
* fix(studio): the timeline scroll gate measures a failed arm once more before failing
* fix(studio): the virtualized timeline gate holds interactions to 58.3 ms
* test(studio): catch teleports and multi-step edit bugs in the edit accuracy bench
* test(studio): share the edit accuracy case harness between single gestures and sequences
* test(studio): keep edit accuracy helpers private unless shared
* test(studio): score plain-CSS edits on pages that load GSAP for other elements
* test(studio): add an idle GSAP axis value so plain-CSS edits are scored on GSAP pages
* test(studio): report a slow undo save as late with its latency, apart from a lost one
* test(studio): sample the gap between back-to-back drags and allow one frame of lag
* test(studio): read the edge from the element's fractional size, so a crop edge is exact
* test(studio): keep waiting for a late save until an undone file stays put for 3 s
* ci(studio): run the edit accuracy grid in 12 shards to stay near 25 minutes each
* test(studio): read edit bench frames just before paint, so unpainted states never count
* test(studio): scope the edit bench frame sampler so page globals cannot clash
* test(studio): let a lagging box catch up the pointer travel it owes, so a held frame is no jump
* test(studio): record every save a sequence makes, even two that land 10 ms apart
* test(studio): end a drag's teleport scoring at its pointer-up, not at the next element's press
* test(studio): bank for each edit accuracy case a run that agrees with the gate's verdict
* test(studio): fail the edit accuracy gate on a teleport or an unmeasured drag
* test(studio): fail the edit accuracy gate when a text edit is not saved or shown
* test(studio): measure but do not gate the edit accuracy cases a known Studio race flips
* test(studio): bank the edit accuracy cases that newly pass with the teleport metric
* test(studio): quarantine the two nudge sequences whose redo write sits at the 2 s limit
* style(studio): cut three bench comments to one line each
* feat(studio): fade handles ride the fade's end and the waveform shows the fade
* refactor(studio): read fade handle keys from one lookup
* fix(studio): fade handle keys stay with the handle and snap past the clip's own edges
* fix(studio): Cmd shortcuts keep working on a focused slider and fade boxes stay on their tabs
* fix(studio): keep a fade handle while it is in use and restore the viewport gate's Chrome path
* fix(studio): a fade's draft clears with its own save and grow drags never shrink it
* fix(studio): saved fades reach the timeline store so the handle stays where it was dropped
* fix(studio): a fade longer than its clip starts a drag or key from the clip's end
* fix(studio): fade comments state the rule, not its history
* fix(studio): fade handles show a real tooltip and a 24 px target
A sound clip's fade-in and fade-out dots now open Studio's tooltip
("Fade in 0.3 s") instead of a native title the clip's own title
competed with, take a 24 px hit box, and paint an opaque white dot that
reads on the audio clip in both timeline themes.
* fix(studio): fade handles sit at the clip's top corners, above its content
The fade-in and fade-out dots now pin to the clip's top-left and top-right
corners instead of the end of each fade, paint above the waveform and
thumbnail layers, and share a short clip's width without overlapping. The
fade ramps are clipped to the clip's rounded corners, and the tooltip
uses the same seconds format as the slider's value text.
* fix(studio): fade dots keep their 10 px size on a very narrow clip
On a clip narrower than two fade hit boxes the flex row squeezed each dot
below 10 px. The dot no longer shrinks and no longer takes the pointer, a
browser check measures its box on a 0.1 s clip in CI, and two drag test
comments now describe the corner handles.
* fix(studio): the Escape key cancels a fade drag wherever keyboard focus is
A press on a fade handle never focuses it, so Escape only reached the
handle's own key handler when something else had focused it, and the
release then saved the dragged fade. The drag now listens for Escape on
the window for as long as it lasts, restores the fade and saves nothing.
* test(studio): trim the fade handle browser check header to what the code cannot say
* test(studio): gate edit accuracy against the base branch baseline
Work in progress: the ratchet and its tests; the CI job follows.
* test(studio): run the edit accuracy gate in CI across eight shards
Shares the built CLI from the Build job, re-runs twice each case that passes on the base branch and failed, and posts one sticky comment from the gate job.
* test(studio): mark the ratchet accuracy check as intentionally branchy for fallow
* test(studio): confirm newly passing edit accuracy cases 2 of 3 before banking them
The shards re-ran only regressions, so one lucky pass of a base-failing case failed the gate as unbanked and was never listed as unstable. Every case whose verdict differs from the base branch, either way, is now re-run twice and judged 2 of 3. A render error fails the gate like an unsettled snapshot, a malformed baseline.json fails the job instead of reading as empty, and the header is 3 lines.
* ci(studio): run the edit accuracy gate for every package the bench builds or imports
* ci(studio): also run the edit accuracy gate for lint, sdk and the Studio and CLI build inputs
* ci(studio): run the edit accuracy gate when a bench package's manifest or exports change
* test(studio): bank the 120 edit accuracy cases that sub-pixel dragging now passes
* ci: install a pinned static ffmpeg on linux instead of apt
* ci: run the jobs that use a shared action when that action changes
* ci: drop the ffmpeg cache so the install is bounded by the download alone
* ci: cap ffmpeg download retries so a Retry-After header cannot stretch the install
* test(producer): run the late-host pip and dogs-captions fixtures in CI
The pip fixture wrote its video start in root time without the global
basis marker that PR 3732 made the contract, so it rendered the empty
host. Mark it global and schedule both fixtures that never ran in CI.
* ci(producer): split regression fixtures across nine shards
dogs-captions runs about eight minutes on its own; a ninth shard keeps
the other shards near their previous wall time.
* ci(producer): run dogs-captions in a nightly regression job instead of on every PR
* ci(producer): base the dogs-captions timing and nightly reason on two CI runs
* feat(plugins): ship portable agent bundles and installation guides
* fix(plugins): align new manifests with current release
* test(plugins): split caption fixture setup and cleanup
* test(plugins): pass render fixture path as an argument
* test(cli): CLI tests run with a home folder of their own, never the user's
* style(cli): format the test home check
* test(cli): the skills test lane also runs with a home folder of its own
* style(cli): format the test home check
* ci: the test reachability map follows the skills lane's new command
* fix(media-use): tests never write into the person's media library
* fix(media-use): guard the media home only in this repo's test runs
* test(media-use): probe the media home from a plain child process
* fix(media-use): keep miss logging best-effort when home is unresolvable
* test(media-use): miss logging survives an unresolvable media home
* docs(media-use): describe the media library without naming an app
* chore(media-use): refresh the skills manifest hash
* ci: stop running CodeQL on merge queue groups, where its upload races the merge
The code scanning rule does not apply to merge queue groups, and the queue deletes
a group ref when it merges, so the upload failed on about half the groups.
Pull requests and pushes to main keep scanning.
* ci: catch merge_group in any trigger form and say the upload race is intermittent
* ci(studio): bank the open-journey counts after #4401 and allow a reasoned raise
* fix(studio): render a missing preview poster whichever of the 404 and the live frame comes first
* ci(studio): gate Studio open and timeline scroll on work counts that only go down
Beside the timeline viewport gate's ms budgets, count the work two Studio journeys do and hold
each count under a checked-in ceiling in perf-ceilings.json: thumbnail renders, page loads and
requests for opening a 12-scene film, and React commits, style recalcs and layouts per scroll
tick. Counts repeat exactly run to run, so a rise names the work that grew.
* ci(studio): hold style recalcs per scroll tick at the CI runner's count
* ci(studio): gate the median scroll run and list which thumbnails rendered
* ci(studio): set work-count ceilings from the CI runner's counts on main
* ci(studio): fail the open count loudly when Studio never starts
* ci(studio): fail a work-count gate that measured nothing
* ci(studio): a scroll counter missing from any measured run reads as not measured
* ci(studio): split the perf ratchet's CLI and page counters into small steps, and let its unit test count
* ci(studio): count scroll work in an untimed pass and commit the open fixture's scenes
* ci(studio): gate only the open journey's counts; scroll counts follow runner speed
* ci(studio): fail a dropped journey and an unreadable base in the work-count ratchet
* ci(studio): split the ratchet's checks into small functions
* ci: editing a PR's text no longer cancels its running checks
CI, regression and Windows render re-run on pull_request edited (for base changes) with cancel-in-progress, so a body or title edit cancelled the running checks and the fail-closed Test aggregator went red on the cancelled run. Only a push or a base change cancels now; a text edit queues behind the running run.
* test(ci): read the workflows folder by path, not as a module URL
Fallow treated new URL('../.github/workflows/', import.meta.url) as an unresolvable import. Use import.meta.dirname and join, as scripts/check-skill-mirror.mjs does.
* fix(media-use): remove clicks and ~25ms gaps at every transcript-cut splice
transcript-cut extracts each kept range to its own final-codec file, then
concat-copies them. That produces two audible artifacts at every cut:
1. No boundary fades. Concat splices raw segment edges together, so the
waveform steps discontinuously and each cut clicks.
2. Encoder priming silence. Each segment is encoded to AAC independently,
so the encoder pads every one with priming samples. Stream-copy concat
bakes that padding in as a silent gap at each splice. Measured 24.4 ms
and 24.1 ms at the two splices of a three-range cut. It accumulates:
the nth splice lands roughly 25n ms late, so splice positions drift
away from the sum of the preceding segment durations.
The second survives the first -- adding fades alone leaves the gap.
Fix: intermediates carry PCM (.mkv for video, .wav for audio-only) instead
of the final codec, so audio is encoded exactly once, at concat. Boundary
fades of 30 ms are applied per segment, scaled down on segments shorter
than 4x the ramp and skipped on degenerate ones.
Measured on the same three-range cut, widest run of digital silence at
each splice:
before 24.4 ms / 24.1 ms
after 0.1 ms / 0.1 ms
--copy is unchanged: stream copy cannot filter, so it keeps producing
final-codec segments and a copy concat. Audio-only output is byte-identical
in duration before and after. scripts/lib test suite: 34/35, the one
failure (lut-preset-provider) pre-exists on main and is unrelated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(skills): regenerate manifest for media-use
* fix(media-use): honor the fadeIn/fadeOut flags fadeFilterFor was passed
The caller already computed {fadeIn, fadeOut} per segment, but
fadeFilterFor still took one parameter and always emitted both
afade ramps -- so a single kept segment (no splice at all) and every
segment's true start/end got faded regardless. Extracted the pure
function into lib/transcriptCutFade.mjs, made it honor both flags,
and added its first unit test: first/middle/last/single-segment
filter strings, no ffmpeg needed, fails against the previous version.
Also installs ffmpeg in the Test: skills CI job (same action the
Producer job uses), so transcript-cut.test.mjs's real-tone RED/GREEN
proof runs in CI instead of skipping silently for lack of the binary.
* fix(media-use): fix the packed-CLI import and lint on transcript-cut.mjs
build-copy.mjs replaces dist/skills/media-use/scripts/lib with a copy
of packages/cli/src/media-use/lib, so transcriptCutFade.mjs needed a
byte-identical twin there (and in MEDIA_USE_COPY_NAMES) or the packed
CLI's transcript-cut.mjs imported a file that no longer existed.
Moved its test alongside, matching every other lib helper's tests.
Also drops three redundant Boolean(args.copy) wrappers (a ternary
condition already coerces) that oxlint's no-extra-boolean-cast
flagged.
* style(media-use): reflow the shortened ext ternary to oxfmt's shape
Dropping the Boolean() wrapper shortened the line enough that oxfmt
0.41 now collapses the ternary onto one line; format:check was failing
on the stale multi-line version.
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Miguel Angel Simon Sierra <miguel.sierra@heygen.com>
* ci: select the remaining orphan tests in their owning runners
* ci: provide FFmpeg for the catalog preview witness
* ci: pass the installed Chrome path to catalog rendering
* test: align creator capability checks with speed ramp support
#4153 added supported speed ramps as rate lanes in data-automation. The
previously unselected capability tests still required preprocessing.
Require the documented rate-lane representation and its precedence over
the constant playback rate, matching the runtime resolver.
* feat(cli): move media-use engine into the CLI
* fix(cli): replace media-use compatibility asset
* fix(cli): replace moved media-use tree
* fix(cli): copy moved media-use files cleanly
* fix(cli): copy media-use engine files individually
* fix(cli): keep audio helper compatibility import
* fix(cli): clear copied skill destinations
* test(cli): preserve moved media-use test paths
* fix(cli): preserve media-use script compatibility imports
* fix(cli): preserve media-use telemetry shim
* fix(cli): resolve packaged media-use LUT assets
* fix(cli): resolve packaged bundled SFX assets
* chore(skills): regenerate media-use manifest
* fix(cli): preserve media-use parity coverage
* fix(cli): cover relocated media-use tests
* chore(skills): regenerate media-use manifest
* fix(cli): format bundled media provider
* fix(ci): run relocated media-use tests with node
* fix(ci): preserve fallow script exemptions
* fix(ci): scope media-use fallow exemption
* refactor(cli): trim media-use essay comments
* feat(cli): index media-use assets for catalog search
* feat(catalog): commit media-use search vectors
* chore(catalog): track media vector metadata
* fix(catalog): validate media vector row order
* fix(catalog): validate media artifact freshness
* fix(catalog): cover media metadata and credits
* fix(cli): reject malformed media rows
* fix(catalog): compare complete media rows
* feat(cli): route media search through media-use
* refactor(cli): trim relocated media-use comments
* fix(cli): make media search runtime-safe
* fix(cli): preserve sfx fallback diagnostics
* fix(cli): probe sfx fallback provider explicitly
* chore(skills): regenerate manifest
* refactor(cli): keep typed media ranker source
* fix(cli): align media search checks with local-first flow
* fix(cli): scope vector audit exceptions
* fix(cli): ship resolver registry beside skill
* fix(cli): keep media-use skill scripts standalone
* test(cli): cover local vector validation
* test(cli): correct media vector fixtures
* fix(cli): prefer exact media keys
* fix(cli): stem exact media keys
* test(cli): skip media lint for snippets without media
* fix(cli): align skills test discovery
Keep the CI skills job's bash discovery as the single source of truth so its empty-list guard covers the exact files Node executes. Make hasMediaVectors private because it has no external callers and needs no Fallow suppression.
* fix(cli): close media-use review gaps
Prioritize literal ids before stemmed ids and cover the collision alongside the plural regression. Add command seam tests for engine discovery, actionable failure, and argv passthrough. Add a deterministic parity check for standalone media-use copies, with documented allowlists for the standalone media-fetch shim and self-contained npx resolver. Keep the bash-discovered skills test list as the CI source of truth.
* fix(cli): lower parity check complexity
Split missing-copy and unallowlisted-drift checks so the parity guard keeps its behavior without a threshold CRAP score.
* fix(cli): close media-use review seams
Run the parity test in the skills job so skills-only edits exercise the same check. Export the verb mapping and inject command dispatch for direct coverage. Keep missing-copy diagnostics separate from drift reads.
Same shape as fix/regression-docker-build-timeout: bun install can print a tarball error and hang instead of exiting (run 35468046055).
This Dockerfile ships a real prod deploy path, so a reviewer should look twice.
* fix(ci): bound the regression image's bun install so a hang cannot eat an hour
bun install printed a tarball error then hung instead of exiting (run 35468046055).
Timeout+retry the install and cap the whole build step at 20 min.
* fix(ci): correct the build step's stated normal duration
Review measured 18 real samples at 2m28s-3m40s (median ~3m), not 5-8m.
Also: neither hang hit the 60-min timeout; both were manually cancelled.