Commit Graph
260 Commits
Author SHA1 Message Date
Miguel Ángel 70ed332a45 test(studio): score edits on GSAP-animated size, scale, rotation and keyframes (#4866)
* 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
2026-10-02 03:26:08 -07:00
Miguel Ángel 6dcc300fd2 fix(studio): Renders dropdown options take a mouse click again (#4890)
* 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
2026-10-01 23:22:06 -07:00
Miguel Ángel e205207583 fix(studio): a text edited in place shows its caret in a scaled preview (#4875)
* 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
2026-10-01 22:31:23 -07:00
Miguel Ángel d218cec091 ci(studio): gates read the base the run measured, and drag frames on a quiet runner (#4872)
* 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
2026-10-01 16:35:49 -07:00
Miguel Ángel 21a29c2ac7 fix(studio): the timeline scroll gate catches dropped frames in the virtualized view (#4855)
* 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
2026-10-01 14:52:39 -07:00
Miguel Ángel 9c19327e38 test(studio): catch teleports and multi-step edit bugs in the edit accuracy bench (#4801)
* 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
2026-10-01 09:42:05 -07:00
Miguel Ángel 07af05c816 feat(studio): fade handles ride the fade's end and the waveform shows the fade (#4844)
* 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
2026-10-01 08:15:10 -07:00
Miguel Ángel e3ac225a9c fix(studio): fade handles show a real tooltip and a 24 px target (#4819)
* 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
2026-09-30 23:55:14 -07:00
Miguel Ángel 097250e11c test(studio): gate edit accuracy in CI against the base branch baseline (#4765)
* 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
2026-09-30 23:22:00 +00:00
Miguel Ángel d572fe3baf ci: install a pinned static ffmpeg on linux instead of apt (#4782)
* 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
2026-09-30 17:07:32 +00:00
Miguel Ángel 625b07fe2d test(producer): run the late-host pip and dogs-captions fixtures in CI (#4625)
* 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
2026-09-28 06:09:39 +00:00
James Russo 0a5e3c7fc4 feat(plugins): ship portable agent bundles and installation guides (#4608)
* 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
2026-09-27 21:58:51 +00:00
James Russo ff6e2103d7 fix(skills): keep plugin workflows on their installed release (#4607)
* fix(skills): keep plugin workflows on their installed release

* docs(skills): clarify plugin manifest discovery

* fix(plugins): suppress stale notices and isolate plugin context
2026-09-27 21:05:28 +00:00
Miguel Ángel 2b1bb8b731 test(cli): CLI tests run with a home folder of their own, never the user's (#4587)
* 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
2026-09-27 12:30:03 +00:00
Miguel Ángel c238abfe6f fix(media-use): tests never write into the person's media library (#4459)
* 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
2026-09-26 14:13:56 +00:00
James Russo edb3747c4c fix(telemetry): honor CLI opt-out in Studio and render CI (#4500)
* fix(telemetry): propagate CLI opt-out to Studio and render CI

* fix(ci): scope container opt-out to HyperFrames
2026-09-25 23:46:40 +00:00
Miguel Ángel fbaa9f9205 ci: stop running CodeQL on merge queue groups, where its upload races the merge (#4492)
* 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
2026-09-25 16:50:55 +00:00
Miguel Ángel 4064b183e6 fix(studio): render a missing preview poster in either order and bank the open counts (#4479)
* 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
2026-09-25 08:39:46 +00:00
Miguel Ángel 0cd69c0a9d ci(windows): fetch LFS files only on the lane whose tests read them (#4477)
* ci(windows): fetch LFS files only on the lane whose tests read them

* ci(windows): name the one LFS file the engine-cli lane reads
2026-09-25 06:45:46 +00:00
Miguel Ángel 73519b6452 ci(studio): gate opening a film in Studio on work counts that only go down (#4457)
* 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
2026-09-25 05:39:44 +00:00
Miguel Ángel de3e51d75a fix(ci): required checks fail instead of skipping when a job they need fails (#4470) 2026-09-25 04:44:16 +00:00
Miguel Ángel 165707b847 ci(windows): run each Windows test group on its own runner (#4468) 2026-09-25 04:44:03 +00:00
Miguel Ángel fc8cb86ef6 ci: check TODO owners, public URLs and source links in comments (#4453)
* ci: check TODO owners, public URLs and source links in comments

* fix(ci): a TODO's issue link must be a github.com issue URL
2026-09-25 04:43:59 +00:00
Miguel Ángel d6adbdc6fc ci: run the required checks on merge queue groups (#4469) 2026-09-25 00:31:22 -04:00
Miguel Ángel 0e34d69641 ci: comment checks grade branches cut before their scripts existed (#4466) 2026-09-24 23:28:36 -04:00
Miguel Ángel 3520c8225c ci: a package file's comment share may not rise (#4445) 2026-09-24 22:06:50 -04:00
Miguel Ángel 1f0cfa7c3e ci: skip Windows runs for player and Studio UI-only changes (#4442) 2026-09-24 21:48:20 -04:00
Miguel Ángel 3194c2ae87 ci: grade the comments each PR touches (#4444) 2026-09-24 20:26:15 -04:00
Miguel Ángel 096a586b41 ci: editing a PR's text no longer cancels its running checks (#4420)
* 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.
2026-09-24 11:19:00 -04:00
James Russo fa4261beea docs: define contributor and agent issue triage (#4374) 2026-09-23 23:45:42 +00:00
Miguel Ángel 29fc95395d fix(skills): keep script imports inside installed skill directories (#4356)
* fix(skills): keep script imports inside installed skill directories

* refactor(skills): simplify standalone module checks

* fix(skills): carry the brand-logo lookup into the media-use copy of media-fetch
2026-09-23 19:09:09 -04:00
Miguel Ángel 922be55e55 fix(skills): preserve captured font subsets (#4358) 2026-09-23 17:19:08 -04:00
Miguel Ángel fb0d33c490 fix(ci): scope catalog dispatch and fail closed on detection errors (#4347)
* ci: scope catalog dispatch and fail closed on detection errors

* fix(ci): distinguish full catalog validation dispatches
2026-09-23 14:56:59 -04:00
Miguel Ángel ca4af78184 ci: dispatch required checks for catalog publication (#4345) 2026-09-23 13:15:38 -04:00
Miguel Ángel b1cf88aeba ci: generate catalog artifacts through a standing publish PR (#4337)
* ci: generate catalog artifacts through a standing publish PR

* fix(catalog): reconcile obsolete publication snapshots

* refactor(catalog): report failures after staging cleanup

* refactor(catalog): separate generation and publication decisions

* fix(catalog): unpublish deleted items with their sources

* docs(catalog): keep scaffolding guidance concise
2026-09-23 12:09:46 -04:00
Miguel Ángel 8027c758d1 ci: compare test reachability against the merge ref's base parent (#4303)
* ci: compare test reachability against the merge ref's base parent

* ci: compare catalog-previews changed items against the merge ref's base parent
2026-09-23 01:29:12 -04:00
dd97478175 fix(media-use): remove clicks and ~25ms gaps at every transcript-cut splice (#4179)
* 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>
2026-09-22 20:28:50 -04:00
Miguel Ángel 9f19d9d325 fix(studio-server): write served files atomically (#4285)
* fix(studio-server): write project files atomically

* fix(studio-server): isolate atomic write temporary files

* test(studio-server): document Windows atomic replacement

* ci: allow change detection checkout to finish

* test(studio-server): assert atomic stamp cleanup without ESM spy
2026-09-22 08:55:45 -04:00
Miguel Ángel 05d09a7452 chore(registry): remove low-use catalog items and variant tails (#4274)
* chore(registry): remove low-use catalog items and variant tails

* fix(registry): remove stale references to deleted catalog items

* style: format catalog fixture

* fix(ci): use surviving catalog preview canary

* chore(ci): allow intentional catalog deletions

* fix(ci): scope fallow audit for catalog fixtures

* chore(catalog): regenerate local search vectors

* chore(catalog): update vector revision

* fix(catalog): validate registry set delta
2026-09-22 07:04:54 -04:00
Miguel Ángel 658d9afa30 ci: shard the Test job by package group (#4286)
* ci: shard the Test job by package group

* ci: keep support shard history for ratchet
2026-09-22 06:25:11 -04:00
Miguel Ángel c029bf4087 ci: require zero orphan tests without a baseline (#4271)
* ci: require zero orphan tests without a baseline

* fix: keep reachability reports diagnostic-only
2026-09-22 01:30:24 -04:00
Miguel Ángel cff467a352 ci: select the remaining orphan tests in their owning runners (#4260)
* 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.
2026-09-22 00:54:41 -04:00
Miguel Ángel a80d733219 ci: run media-use tests when media-use changes (#4259) 2026-09-21 22:05:44 -04:00
Miguel Ángel 86e27b00f6 ci(studio): ratchet colour contrast of timeline tokens (#4257)
* ci(studio): ratchet colour contrast of timeline tokens

* refactor(studio): clarify contrast ratchet branches and state citations

* fix(studio): resolve contrast baseline from the repository root

* test: parse contrast channel tokens without color wrappers

* ci: keep contrast gate artifacts outside runtime sources

* test: honor strict indexed access in contrast scripts

* test: require removal of zero contrast baseline entries
2026-09-21 21:35:23 -04:00
Miguel Ángel 0be08f34a2 ci: ratchet test reachability across workflow filters (#4253)
* ci: ratchet test reachability across workflow filters

* fix: fail closed on skipped detection and quoted selectors

* refactor: separate reachability parsing decisions

* fix: keep comments and runner prefixes out of reachability

* refactor: bound reachability parser complexity

* fix: reject conditional shell runner blocks

* fix: reject conditional shell constructs in test routing
2026-09-21 21:14:49 -04:00
Miguel Ángel 3091dcb787 feat(cli): route media search through media-use (#4230)
* 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.
2026-09-21 18:35:47 -04:00
Miguel Ángel 2060e13f2d fix(ci): bound the gcp-cloud-run image's bun install so a hang cannot stall it (#4206)
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.
2026-09-19 18:33:06 -04:00
Miguel Ángel 420fc118cc fix(ci): bound the regression image's bun install so a hang cannot stall it (#4204)
* 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.
2026-09-19 18:32:54 -04:00
Miguel Ángel f24ff83834 feat(catalog): add the seven 3D-motion pieces as installable registry blocks (#4056)
* feat(catalog): add the seven 3D-motion pieces as installable registry blocks

Each piece ships as a registry block with a CDN poster and preview, a live
catalog detail page and its source. Scripts travel inside the catalog payload
(shared vendor libraries fetched once, item scripts inline, the one HDR as a
data URI) because the docs host only serves fonts, images and JSON.

NO_ESSAY_COMMENTS_OK: the long comment blocks are in vendored libraries and
the ported frost source under registry/blocks, not hand-written here.
HF_MAX_NONLFS_KB=6000: four hosted texture PNGs under docs/public/catalog/items
are runtime assets the docs host must serve as files.
fallow skipped locally: it audits against origin/main and this branch is
stacked on an older main.

* chore(catalog): regenerate gallery data and nav after rebasing onto main

* fix(catalog): read the install manifest through the bounded project reader

* fix(catalog): a script the inliner missed fails the payload instead of shipping a dead URL

* refactor(catalog): the leftover-script check reuses the reference finder

* fix(catalog): frost and glass textures load from the CDN and the iteration presets are gone

* chore(catalog): regenerate the frost and glass payloads and nav

* fix(catalog): frost and glass textures are hosted through the registry url field, not hardcoded

* chore(catalog): regenerate the frost and glass payloads against the hosted texture URLs

* fix(catalog): the hosted-reference rewrite refuses a bare name it cannot safely replace

* ci(catalog): verify changed catalog payloads in headless Chrome and check the docs catalog

* feat(catalog): every item page follows the shared layout with a Tune panel and tabs

* fix(catalog): let the texture-mask item host its masks so its tile plays live

* chore(catalog): regenerate item pages, gallery data and the texture-mask payload

* chore(catalog): regenerate item pages after merging main

* fix(catalog): declared-only variables get the Tune panel, Code and Docs tabs capped

* feat(catalog): give the blend-difference caption a demo scene so its tile plays live

* chore(catalog): live payload for the blend-difference caption

* perf(catalog): heavy 3d pieces compile and draw before ready, not on the first visible frame

* feat(catalog): the avatar promo card hosts its videos, audio and fonts on the CDN

* feat(catalog): the avatar promo card tile plays live from its CDN-hosted media

* chore(catalog): the avatar promo card plays live, no allowlist left

* chore(catalog): shorten two comment blocks

* chore(catalog): trim first-party comment blocks to the comment-check limits

* chore(catalog): shorten two frost source comments

* fix(catalog): the Docs tab renders as prose again, and the detail page honours reduced motion

Frame-mode pages carry no prose wrapper, and the detail component sat inside
not-prose, so the Docs slot's headings, paragraphs, links and lists had no
styling at all. The slots now carry their own prose classes and the chrome
around them keeps not-prose.

The recorded clip also gets the reduced-motion guard the docs-snippet check
requires, and the generator reads stability from the source metadata type so
the scripts typecheck passes.

* fix(catalog): docs-tab paragraphs render as blocks

Mintlify emits paragraphs as inline spans, so two paragraphs in the Docs slot ran together.

* fix(ci): drop the duplicate docs_catalog job output

git's line-based auto-merge silently duplicated the outputs key when
main and this branch each added it on non-adjacent lines during the
rebase; oxfmt's yaml parser rejects the repeated map key.

* fix(catalog): close four CodeQL alerts in the frost source

* chore(catalog): allowlist the avatar-promo-card CDN asset deletions, format sync-docs-catalog

check-no-main-deletions.mjs was rejecting 11 local mp4/font files this branch
already deleted in favor of CDN hosting; scripts/sync-docs-catalog.mjs had one
line over the 100-col print width from an earlier edit in this branch.

* fix(catalog): detail tabs use docs prose spacing, Install first; previews skip no-WebGPU runners

* fix(catalog): the docs tab scrolls with the page instead of a capped inner box

* refactor(catalog): split the payload asset and verify scripts into single-purpose helpers

* refactor(catalog): split the preview generator into single-purpose helpers

* ci(catalog): fail when the committed catalog differs from generator output

The docs host serves the committed catalog and nothing regenerates it, so a stale
copy shipped silently. The catalog payloads job now generates into a temp dir and
compares it with the committed tree.

* chore(catalog): regenerate docs/public/catalog with the refactored scripts

One full regeneration from an empty output tree with the current scripts. The tree grows
from 58.4 MB to 65.1 MB. Two hosted assets are about 2 MB each (an avatar png and a wav);
the same bytes are already committed under assets/, so HF_MAX_NONLFS_KB is raised for
this commit only.

* fix(catalog): run the catalog script tests under the runner they are written for

The rebase put three node:test files in the vitest half of test:scripts, where they report
"No test suite found". They now sit in the node --test list.

* fix(catalog): a WebGPU item without an adapter shows its recorded clip and says why

* ci(catalog): the drift check runs from a committed fetch mirror with no network

Generation now serves every font and CDN script fetch from scripts/catalog-fetch-mirror and fails
when a fetch is not in it, even if the caller swallowed the error. CATALOG_FETCH_MIRROR=record
refreshes the mirror from the live network. The docs_catalog filter also watches packages/core,
packages/producer and the mirror, and the 13 re-hashed font files are listed in ALLOWED_DELETIONS.

* chore(catalog): clean up the temp font cache and sort the mirror index without a locale

* fix(catalog): hide Tune when the clip stands in, time out the adapter request, share one check

* fix(catalog): the adapter probe survives a sync throw and a hang; test the clip guard

* fix(catalog): keep the adapter probe inside the component, Mintlify evaluates only exports

* fix(catalog): run the detail and page generator tests in CI; no Tune flash while probing WebGPU

* docs(catalog): credit vendored libraries and make each SKILL.md list what the block installs

CREDITS.md names every vendored dependency with its upstream and licence, and flags the two leaf
textures whose origin is still to be confirmed. Each SKILL.md Files table now comes from the block
manifest and its install and render paths point at compositions/<name>/. A test checks that every
path a SKILL.md names is a manifest file that exists.

* fix(catalog): a declared WebGPU item passes verify by its manifest tag, not by console wording

verify-catalog-payloads forgave a missing adapter only by matching the markup and the message, which
library-reported errors and widgets never satisfy. The webgpu tag in the item manifest is now the
owner: a tagged item ignores its page and console errors, an untagged one gets no forgiveness, and a
marker payload with no html is listed instead of crashing the run.

* docs(catalog): correct the credits per block and guard the mount and render paths

CREDITS.md states the three.js version and vendored-or-CDN status per block, adds d3-delaunay,
Clipper on the CDN and the Google Fonts loads, and lists every texture with no known origin.
The SKILL.md test now also checks the mount and render paths against the manifest composition.

* test(catalog): read each block manifest once in the SKILL.md file test

The two checks shared a copied manifest read, which fallow flagged as a clone with a high score.

* fix(catalog): commit the hosted item files a gitignore rule kept out, and check git not the disk

The compositions/ and *-ad/ ignore rules matched folder names under docs/public/catalog/items,
so 33 hosted fonts, textures and marks were generated but never committed and would 404 on
the docs host. The rules now have a negation for that tree and the files are added.

The drift check read the checkout, where git-ignored files still exist, so it passed locally
and failed only in CI. It now unpacks HEAD with git archive and compares that.

* fix(catalog): a tile keeps its poster until the composition has painted

* fix(catalog): a tagged WebGPU item forgives only the adapter-absent error, and test the wiring

The manifest webgpu tag now decides whether an item may lack an adapter, and the error wording
decides which lines are that absence. Any other page or console error on the item, and every
failed request, still fails. The per-item decision is one exported function the tests drive,
marker payloads are parsed by a tested helper, and the SKILL.md variable counts are checked
against their tables (cuboid-carousel said forty-nine for forty).

* fix(catalog): the pinned group's posters load eagerly at high priority

* fix(catalog): type the tile reveal test so the scripts typecheck passes

* fix(catalog): stop the adapter signature matching an unrelated failed adapter request

failed to request .*adapter also matched errors about other adapters. It now matches only a
request for the WebGPU adapter, so a tagged item cannot hide an unrelated failure behind it.

* test(catalog): a tagged item still fails on an error about some other adapter

* feat(catalog): the install command sits above the preview, as on the live catalog

* fix(catalog): find the install slot through Mintlify's child wrapper

* fix(docs): catalog copy buttons keep their size when the label swaps to Copied

* refactor(docs): name the shown copy label once in the catalog copy button

* fix(docs): primary catalog button keeps a readable label on hover and after copy

* fix(docs): inset the catalog tab strip and tab content to match the boxed panels
2026-09-19 16:53:29 -04:00
Miguel Ángel 8d07221156 feat(scripts): fail a PR whose After capture is a Before capture (#4177)
* feat(scripts): fail a PR whose After capture is a Before capture

The captures check now downloads each Before and After asset in the PR
body and fails when any After asset has the same content hash as a
Before asset, or when an asset cannot be downloaded. A Before clip
attached under After passed the old check because it only looked for
media, not for what the media was.

* refactor(scripts): return the hashed captures directly

* fix(scripts): the capture hash check only fetches GitHub attachments

The check fetched any URL a PR body named and followed redirects, so a
body could make the runner request an internal host. It now downloads
only github.com/user-attachments assets and follows redirects only to
GitHub and its signed storage. A 429, 5xx or network error is retried
with backoff, other statuses fail at once, and unreadable assets and
identical assets are reported under separate headings.

* fix(scripts): cap capture downloads, match GitHub's asset bucket, pass forks through

The redirect allowlist now names GitHub's asset bucket instead of all of amazonaws.com.
Each asset and the whole check are capped in bytes while streaming.
A fork PR whose capture cannot be downloaded is skipped with a named reason, never failed.

* fix(scripts): one download deadline, https only, same link caught before fetching

Reviewer findings on the capture hash check:
- each asset gets one deadline across its retries
- downloads run one at a time so a large asset cannot starve a sibling
- a link under both headings is reported without a download
- a read error is no longer reported as a download failure
- the CLI fork skip and exit code are tested through a preloaded fetch

* fix(scripts): a byte-cap refusal blocks even on a fork, retries refund their bytes

- our own cap tripping is reported apart from an unreadable download and always fails
- a failed attempt gives its bytes back to the shared budget
- trailing punctuation and query strings no longer change which asset a link names
- more than 12 capture links is refused, and the job timeout is raised to fit

* fix(scripts): strip markdown wrappers from capture links, count shared links once

The twelve-link limit is tested at its edge.

* refactor(scripts): split the capture download code into small functions

Fallow scored the download, redirect and reporting functions as untested because the scripts
suite runs under node --test. Each function is now small enough to pass without coverage.
Links the duplicate check cannot compare are now named: an http attachment link is refused,
other media links are listed.

* fix(scripts): compare links by identity, name the refusal, pin three behaviours

An explicit port or upper-case host no longer reads as an uncomparable link.
2026-09-19 12:45:17 -04:00