Commit Graph
4643 Commits
Author SHA1 Message Date
Vance IngallsandClaude Opus 5 48aaa2bbb7 fix(vfx): join the preview capture to the seek-completion barrier, not race it
`paintVfx` fired `capturePreviewThenPaint` and forgot it, so nothing the CLI
awaits waited for a `self` chain's paint. Every snapshot-family command
(`snapshot`, `check`, `compare`, `validate`, `layout`) seeks through
`seekCompositionTimeline`, which drains `window.__hfWaitForSeekCompletion`
and then allows one settle race before screenshotting — and only the
`"hf-seek"` event's `waitUntil()` could put anything in that barrier. The
screenshot therefore landed on however many hosts happened to have finished,
which varied with host count and machine speed.

Extracts the "track, untrack on settle, retain the first rejection" block
`dispatch()` built inline into an exported `registerSeekCompletion`, and has
`paintVfx` register the preview capture through it. Also awaits the per-host
paints with `Promise.all` instead of sequentially: N hosts cost about two
animation frames now, not 2N.

Measured on the pre-fix bundle with two `self` hosts, both output canvases
read `[0,0,0,0]` at the moment the barrier returned; they painted only
because the settle race that follows happened to be long enough. Both tests
added here fail on that bundle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 01:04:57 -07:00
Vance IngallsandClaude Opus 5 d243cd92aa refactor(telemetry): vfx_node_count counts hosts, not chain nodes — rename to vfx_host_count
The field increments once per `data-vfx-chain` attribute occurrence, never
per node inside a chain's JSON, so the name promised something the scan does
not measure. Renames the internal `vfxNodeCount` and the wire property
`vfx_node_count` to `vfxHostCount`/`vfx_host_count` across the orchestrator
scan, the perf summary, the CLI render path and the telemetry event. No
dashboard consumes the field yet, so the wire rename costs nothing now and
would cost a migration later.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 01:00:19 -07:00
Vance IngallsandClaude Opus 5 c849383b16 fix(vfx): an exporter-emitted .hf-vfx-out gets the box the spec fixes for it
findOrCreateOut adopted a canvas the exporter had already emitted without
applying the layout cssText, so it kept the default position:static;
display:inline and wrapped to the line AFTER the layer-wide .hf-vfx-src
canvas — painting a second, misplaced copy of the layer one full layer
height below the host. Measured on retro-wave #main-l6-text: .hf-vfx-src
rect top 315.1 h 93.0, .hf-vfx-out rect top 409.2, both style=null,
position static (ae-mcp findings Task 3.5b). Hosts with no emitted
out-canvas were never affected, which is why only this one host moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 22:10:11 -07:00
Vance IngallsandClaude Opus 5 dcfbda9fd9 fix(vfx): paint self-capture hosts under hyperframes snapshot, not only the engine path
`paintVfx(t, { engineMode: true })` armed the page-composite protocol and
returned, betting that every capturing chain runs under `frameCapture.ts`.
`hyperframes snapshot` (and check/compare/validate/layout, and Studio's
thumbnail capture) seek through the same `renderSeek`, never read
`__hf_page_composite_pending` and never call `__hf_page_composite_resolve` —
so a `self` chain painted nothing there, in silence, because its content
lives in a `layoutsubtree` canvas the browser never paints directly.

Engine mode now arms the preview-side capture as well and paints on whichever
completes first. A seek token stops a superseded async capture from repainting
a newer frame, and a registry-identity check stops it painting an entry
`initVfx` already released. `resolveVfxCapture` stays non-idempotent on
purpose: the engine resolves only after its own before-capture hooks have
mutated the DOM, so its capture is still the authoritative one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 21:13:11 -07:00
Vance IngallsandClaude Sonnet 5 69a7545424 fix(vfx): structural DOM predicates instead of realm-bound instanceof
vfx.ts checked `instanceof HTMLCanvasElement`/`HTMLElement` directly, which
returns false for inner-realm nodes when the Studio previews across an
iframe realm. domRealm.ts had no canvas predicate yet, so this adds
isCanvasElement mirroring the existing isImageElement pattern, then swaps
all three vfx.ts checks to the structural predicates the lint guard requires.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 20:21:33 -07:00
Vance IngallsandClaude Sonnet 5 3de933e9d1 fix(vfx): runtime tsconfig typecheck passes
Under noUncheckedIndexedAccess, resolveCaptureSource's null return
widened VfxEntry.src past its VfxCaptureSource | undefined type; the
function is internal-only, so its return type moves to
VfxCaptureSource | undefined instead. Indexing PingPong.framebuffers
by a non-literal index produced | undefined against
WebGLFramebuffer | null, coalesced to null. No behavior change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 20:16:15 -07:00
Vance IngallsandClaude Opus 5 e7f154730e fix(vfx): register chain hosts that arrive with a sub-composition mount
`initVfx(document.body, fps)` ran once, synchronously, three lines after
`void loadExternalCompositions(...)` — so every `data-vfx-chain` host that
enters the DOM only when a `data-composition-src` mount resolves was never
registered. An unregistered host paints nothing and reports nothing: no
`[HyperFrames] composition script error`, the `.hf-vfx-out` canvas keeps its
300x150 default, and `paintVfx` never even arms the page-composite flag.
Measured on a real multi-file export served raw: `#main-l5-text`'s output
canvas stayed 300x150 and `window.__timelines` never gained the sub-comp.

Re-scan in the loader's `.finally`, before `maybePublishRenderReady()`, so
registration precedes readiness. `initVfx` now releases the outgoing
registry's programs, capture textures and ping-pong targets first — the
output canvas hands back the same WebGL2 context on a re-scan, so the
previous pass's GL objects would otherwise leak.

Two browser cases in `vfxDeterminism.test.ts`, both against the real runtime
bundle: a host whose `.hf-vfx-in` IS the mount (already worked — the mount
replaces the wrapper's children, not the wrapper, so the registered source
node survives), and a host that arrives INSIDE a mounted sub-composition
(failed with `armed: false` and no error; passes now).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 20:06:39 -07:00
Vance IngallsandClaude Sonnet 5 288ad74f4d docs(vfx): data-vfx-chain guide — element shape, capture table, Studio flag, known constraints
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 18:55:50 -07:00
Vance IngallsandClaude Sonnet 5 9936cdd625 feat(telemetry): vfx node count, capture class and types on render_complete
Threads vfxNodeCount, vfxCapture and vfxTypes from a static scan of the
compiled composition's data-vfx-chain hosts (mirrors the existing
compositionElementCount/colorGradingCount scan) through
RenderPerfSummary.drawElement into the render_complete event, next to
de_capture_mode. Exports chainCapture/parseVfxChain/HfVfxCapture from
@hyperframes/core's index for the producer to consume. The PostHog
dashboard tile is not created here — left for the coordinator.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 18:53:26 -07:00
Vance IngallsandClaude Sonnet 5 a73d847b33 fix(vfx): size the output canvas from the host's layout box, not its transformed AABB
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 18:36:33 -07:00
Vance IngallsandClaude Sonnet 5 120360852d test(vfx): seek determinism and CPU-reference PSNR for fractal-noise
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 18:35:42 -07:00
Vance IngallsandClaude Opus 5 b1d1e3f51f test(vfx): chain order is nesting order; preceding CSS effects are captured
...and following ones apply outside. The plan's one-line message for Task 2.4
is 105 characters; commitlint caps the header at 100, so the tail moved here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 18:29:05 -07:00
Vance IngallsandClaude Opus 5 cff32190d1 feat(vfx): displacement-map kernel (self-referential map)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 18:20:20 -07:00
Vance IngallsandClaude Opus 5 5d738d91dd feat(vfx): wave-warp kernel (Sine)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 18:16:15 -07:00
Vance IngallsandClaude Opus 5 42e5689732 feat(vfx): self capture through layoutsubtree with the page-composite readiness protocol
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 18:08:25 -07:00
Vance IngallsandClaude Opus 5 2a9c2f951a feat(vfx): fractal-noise kernel (Basic/fBm approximation) with CPU reference
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 18:00:49 -07:00
Vance IngallsandClaude Opus 5 1272e8c3c6 feat(runtime): paint data-vfx-chain canvases on every seek
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 17:51:01 -07:00
Vance IngallsandClaude Sonnet 5 f930cd95f3 feat(core): data-vfx-chain defs and chain parsing (mirrors data-fx-chain)
Mirrors audioFx.ts's shape for the per-pixel VFX chain: a versioned
JSON chain attribute (data-vfx-chain), a def registry with typed
params (number/enum/bool/ref), and parse/serialize/normalize helpers.
HF_VFX holds one def, fractal-noise, with a placeholder frag; the
kernel and its consumers (packages/core/src/runtime/vfx.ts) land in
Task 1.2/1.3 of the vfx-chain-runtime plan, so vfx.ts is added to
.fallowrc.jsonc's entry list for now.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 17:32:16 -07:00
James RussoandClaude Opus 5.5 ec3bc93b90 docs: alphabetize supported coding agents (#4297)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-22 22:21:34 +00:00
Brun3y 105984cac4 feat(cli): add IBM Bob runtime support (#3930) 2026-09-22 22:01:42 +00:00
Miguel Ángel d4485b51f1 chore: release v0.8.62 (#4293) v0.8.62 2026-09-22 16:12:35 -04:00
James RussoandClaude Opus 5.5 16959deeac fix(studio): don't fail a shadow reload while the tab is hidden (#4292)
The shadow reload promotes on frame-driven readiness (the player's
paint-and-idle, then two requestAnimationFrames) but failed on a
wall-clock 15s setTimeout. A hidden tab renders no frames, so every
external edit made while Studio was in the background failed with
"it took too long to load", the toasts stacked, and the stale preview
stayed up even after returning to the tab.

The budget now only runs while the document is visible: hiding the tab
clears the timer, and showing it again restarts the full budget for the
pending shadow, which then promotes normally.

Fixes #4290

— Rames Jusso

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-22 20:05:15 +00:00
Miguel ÁngelandJake Moran 0945de83c7 feat(registry): text-match-cut (#4288)
* feat(registry): add text-match-cut — one text becomes another mid-move on a single 85 % curve

Video primitive reversed from an After Effects tutorial (Editing Snapped, "The Match
Cut Technique Every After Effects User Needs"), measured per frame at 60 fps. Both
texts ride one keyframe pair, cubic-bezier(.85, 0, .15, 1) over 2 s, and hard-swap at
the speed peak (1.0 s). A `mode` variable picks the property that carries the cut:
scale down (the tutorial), scale up, tracking in, or a same-direction slide in four
directions. Optional cyan-to-blue gradient sweep on its own 87 % curve, navy or
transparent background.

* chore(registry): integrate text-match-cut with current main

---------

Co-authored-by: Jake Moran <jakemoran2011@gmail.com>
2026-09-22 12:14:46 -04:00
Vance IngallsandMiguel Angel Simon Sierra 8d2b6e7bdc chore: release v0.8.61 (#4283)
Co-authored-by: Miguel Angel Simon Sierra <miguel.sierra@heygen.com>
v0.8.61
2026-09-22 11:42:28 -04:00
b22bc2c979 feat(registry): extended-keyframe (#4281)
* feat(registry): add extended-keyframe, the never-fully-lands arrival with motion blur

A hyperframes:component. An element covers most of its move in the first fifth of the
duration and creeps the rest to the last frame (After Effects' extended-last-keyframe
technique), exits along the vector it arrived on, and carries shutter motion blur:
slides via the bundled feOffset filter smear, scale beats via a velocity-driven blur.

  var a = attachExtendedArrival("#word", tl, { at: 0.3, from: { x: -1400 } });
  attachExtendedArrival("#next", tl, { at: a.end, from: { scale: 0.35 } });

Direction and property agnostic (x / y / scale). The ease is exposed on its own as
EK.ease() for any tween, with EK.next() for sequencing and EK.gate() as a numeric check
(single velocity peak, no rises after it, tail above zero to the last frame).

registry/registry.json, docs/docs.json and docs/public/catalog-index.json regenerated
with the two generator scripts; they also pick up drift already present on main.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(registry): publish live extended keyframe payload

* fix(registry): tear down extended keyframe blur

* fix(catalog): refresh extended keyframe payload

* fix(catalog): preserve merged catalog entries

* fix(catalog): regenerate catalog index

* fix(registry): trim extended-keyframe comments

* chore(registry): retrigger checks

* fix(registry): refresh extended-keyframe artifacts

* fix(catalog): refresh extended-keyframe payload

* fix(registry): refresh extended-keyframe artifacts against current main

---------

Co-authored-by: Jake Moran <jakemoran2011@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 11:41:48 -04:00
Miguel Ángel 2c71a3a112 fix(catalog): registry set-delta check accepts intentional additions (#4287)
check-registry-set-delta.mjs only recognized removals: an added name in
registry.json had no matching allowance, so any pure-addition PR failed
with "unexpected in registry.json: <name>" even though the item's own
registry-item.json was added in the same diff. Mirror the deletion rule
for additions: a name is expected when its own registry-item.json was
added or deleted in the same diff, same condition either side already
required.

CI's audit tool flagged the CLI entry itself (high-crap-score rule,
scripts/check-registry-set-delta.mjs:30 main, CRAP 42.0 threshold 30.0,
cyclomatic 6): main() shelled to git and parsed JSON inline. The git/fs
boundary is now injected as runRegistrySetDeltaCheck(base, { run,
readRegistryJson }), covered directly by a test with a fake run/read, so
main() is a thin print-and-exit wrapper.
2026-09-22 11:24:51 -04:00
Miguel Ángel e5a0370727 feat(studio): transition seams blur and carry a badge (#4280)
* feat(studio): transition seams blur and carry a badge

* fix(studio): keep labelled transitions on one row

* fix(studio): render transition crossfade zone

* fix(studio): gate seams on transition labels

* fix(studio): bridge GSAP transition markers to clips

* fix(studio): clear transition seam CI failures

* fix(studio): register transition frame classes
2026-09-22 08:59:53 -04:00
Miguel Ángel 189bead8cb fix(studio-server): prefer Puppeteer's cached Chrome for thumbnails (#4272)
* fix(studio-server): prefer Puppeteers cached Chrome for thumbnails

* style(studio): format browser resolver test

* chore(studio): refresh checks

* chore(studio): refresh checks

* chore(studio): refresh checks

* fix(studio): resolve Puppeteer cache by platform
2026-09-22 08:59:28 -04:00
Miguel Ángel 94609daf44 feat(studio): audio pills show real peaks and loudness (#4282)
* feat(studio): audio clips draw real peaks shaded by loudness

The pill fill is the track tint at 70 percent, and each bar's opacity follows the cached server peaks. Muting greys the pill in place. Linked video audio shows a 1px tick.

* style(studio): format the audio waveform markup

* fix(studio): map audio peaks across the full clip width

* fix(studio): keep waveform visible through fade wedges

* fix(studio): keep silent waveform bins visible

* fix(studio): keep waveform baseline visible edge to edge

* fix(studio): keep waveform visible over fades

* fix(studio): keep quiet waveform bars visible

* fix(studio): paint waveform above fade wedges

* fix(studio): fit waveform inside audio clip

* fix(studio): preserve waveform contrast tokens

* test(studio): ratchet waveform color literals

* fix(studio): keep loudness floor internal

* chore(studio): document intentional fallow findings

* refactor(studio): share fade handle rendering

* fix(studio): pass fade handle styles explicitly

* refactor(studio): split waveform drawing stages

* test(studio): cover waveform canvas painting
2026-09-22 08:58:21 -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 5ca2e78383 feat(studio): render geometry-sized video filmstrips (#4234)
* feat(studio): render geometry-sized video filmstrips

* fix(studio): keep poster tier during timeline scroll

* fix(studio): cap rendered thumbnail tiles

* chore(studio): refresh filmstrip checks

* chore(studio): refresh filmstrip captures

* chore(studio): refresh four-times captures

* fix(studio): stop capping filmstrip render tiles at 33

computeThumbnailStrip no longer clamps frameCount; the decode-side cap already lives in quantizeThumbnailFrameCount. VideoThumbnail only acquires the rich filmstrip lease when requestFrameCount > 1, so a narrow clip issues one decode job instead of two identical ones. Deletes the unreachable duplicate defaults in createVideoThumbnailRequest.

* style(studio): format filmstrip changes
2026-09-22 06:45:11 -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 53153d4a54 feat(registry): examples show the most-installed blocks (#4284)
* feat(registry): examples show the most-installed blocks

* fix(docs): escape block placeholder in examples

* fix(docs): escape template placeholder in examples
2026-09-22 05:46:33 -04:00
Vance IngallsandClaude Sonnet 5 24c3e197ef fix(studio): cutover gate classification, SSE cross-project bleed, benign races (#4269)
Implements the fixes from the 2026-09-21 cutover dashboard read
(plans/sdk/2026-09-21-cutover-dashboard-fixes-plan.md):

- sdk_cutover_failed now carries `family` and `error_kind`
  (network/conflict/http/sdk). The one production gate event was a raw
  `TypeError: Failed to fetch` from an unwrapped fetch (readProjectFile /
  writeProjectFile's preflight), which an instanceof-only classifier would
  still file as "sdk" and trip the rollback gate — added a message-based
  fallback for the browser's fetch-rejection strings.

- studio:sdk_session_unavailable's `network` reason now carries `elapsed_ms`
  and `hidden` (document.visibilityState at rejection), separating a
  near-instant policy block (CSP/PNA/extension) from a dropped connection
  the tab or server going away mid-flight.

- The ownership-race branch in useSdkSession's open effect now emits
  sdk_session_superseded instead of sdk_session_unavailable{stage:ownership}
  — it's a benign race (identity moved on mid-open), not a broken
  precondition, and was being counted as one on the dashboard.

- Root-caused and fixed the read-404 "storm": /api/events (CLI host) and
  the vite-dev hot-reload channel are one connection per SERVER, not per
  project. A stale tab left open after `hyperframes preview` restarts on a
  different folder (same port) received every save the CURRENT project
  made, reloading its preview (which then 404s) and re-reading its own
  composition on each one. Both transports now carry `projectId`; the
  client-side coordinator drops a delivery whose projectId doesn't match
  the tab's (absent field still passes, for one release of skew).

- studio-server's file routes report `why` on 403/404: `project_dir_missing`
  (new — the CLI host's static resolveProject kept answering for a project
  whose folder was renamed/deleted under a running server, which used to
  fail closed as an indistinguishable path-traversal 403) vs `nul` vs
  `outside_project`.

- absent_or_empty reads now carry `path_in_tree` (against the caller's
  loaded file tree). Instrumentation only, per the graveyard page
  (clearing-activecomppath-on-tree-absence) that refuted the "clear
  activeCompPath" fix this was the proposed next step for.

Tests: unit coverage added for every change (sdkCutover.test.ts,
useSdkSession.lifecycle.test.tsx, useExternalFileChangeCoordinator.test.tsx,
studioServer.test.ts, files.pathSafety.test.ts). oxlint/oxfmt clean,
tsc --noEmit clean on studio/studio-server/cli.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 01:40:47 -07:00
01734fae3c feat(registry): colorama-wipe (#4277)
* feat(registry): add colorama-wipe, a colour-mapped feathered wipe reveal for headlines

A hyperframes:component recreating the After Effects "shiny wipe": masked text +
Compound Blur + Colorama over a moving gradient, with a crisp duplicate fading on top.
One call per text element:

  attachColoramaWipe("#headline", tl, { at, in, hold, out, cycle, feather, tilt, blur })

adds a single linear clock tween to the host timeline and paints the effect on two
canvases over the element's box; the element is hidden (visibility) with its layout
kept. All sizes scale from the element's font size. Deterministic: hash noise keyed by
frame, no Math.random, no clocks; seeks outside the window paint an empty stage.

registry/registry.json, docs/docs.json and docs/public/catalog-index.json were
regenerated with the two generator scripts as the contributing guide asks; they also
pick up drift already present on main.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(registry): publish live colorama wipe payload

* style(registry): format colorama wipe demo

* fix(registry): tear down colorama wipe on timeline kill

* fix(catalog): refresh colorama wipe payload

* fix(catalog): preserve merged catalog entries

* fix(catalog): regenerate catalog index

---------

Co-authored-by: Jake Moran <jakemoran2011@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 03:55:03 -04:00
Miguel Ángel 9603fe16e0 Merge pull request #4279 from heygen-com/release/v0.8.60
chore: release v0.8.60
v0.8.60
2026-09-22 03:45:16 -04:00
Miguel Ángel c51a0d7069 chore: retrigger release checks 2026-09-22 03:27:39 -04:00
Miguel Ángel b12e767e5a chore: release v0.8.60 2026-09-22 03:11:40 -04:00
Miguel Ángel 5203312c34 feat(studio): timeline clips take the designed material (#4270)
* feat(studio): timeline clips take the designed material

Picture clips use an 8px radius and audio clips a 21px pill.
Clips sit one step above the lane. The label is a chip that
drops under 60px. Selection keeps the inset outline plus a
1px inner highlight. Hover background is the only transition.

* style(studio): format timeline clip shadow

* fix(studio): restore handle-bar transition and de-pin box-shadow test
2026-09-22 02:15:55 -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 867787b2f9 feat(studio): menus and popovers paint instantly (#4268)
* feat(studio): menus and popovers paint instantly

Menus and popovers open and close with no fade. Hover colour is unchanged.

* fix(studio): fade menus in from a visible shape

Menus, popovers, and dropdowns start at opacity 0.8 and scale 0.97, then settle in 120ms. They close in 90ms. Tooltips use the same shape in 100ms.
2026-09-22 01:05:00 -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 c27bdda7d9 fix(engine): read extensible PCM and float WAV headers (#4267) 2026-09-22 00:15:42 -04:00
Miguel Ángel d5e00c0e66 feat(registry): week in merges leaderboard block (#4264)
* feat(registry): add a week-in-merges leaderboard block

Authors race across the row at a speed set by their merge count, bouncing at both edges.

* chore(registry): cover week-in-merges in the catalog search index

* style(registry): format the week-in-merges block

Wrap the lines oxfmt rejects so the format check passes.

* fix(registry): regenerate the week-in-merges catalog payload

The format pass left the committed payload off the generator output.

* feat(registry): cap week-in-merges at twelve rows

The block races at most rows_max authors, highest merge count first, and writes one line for everyone left out. The default is 12, so a long list cannot shrink the rows.

* fix(registry): make week-in-merges speed follow merge count

A row now travels the leader's pace times its merges over the leader's count. Only a zero stays on the floor, so a near-tie no longer looks like last place.
2026-09-21 23:35:55 -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 2cc384f1fe feat(studio): draw icons that say what the feature does (#4262) 2026-09-21 21:05:20 -04:00
Miguel Ángel 3e63de4778 fix(studio): keep timeline selection ring inside clip (#4251)
* fix(studio): keep timeline selection ring inside clip

* test(studio): cover active dragging selection ring

* fix(studio): keep selection rings above thumbnails
2026-09-21 21:03:56 -04:00