Commit Graph
3370 Commits
Author SHA1 Message Date
Miguel Angel Simon Sierra c264cf266f fix(studio): stop lane-header clicks from reaching the track row
The prev/next keyframe chevrons and the group toggle diamond let their
click bubble to the ancestor track row, so seeking to a keyframe also
reselected the track. The disclosure caret and the eye already stop it;
these now match.

Truncated labels (layer name, track label, group label, value readout)
also carry a title so the full text is reachable on hover.
2026-07-27 19:52:06 +02:00
Miguel Angel Simon Sierra 8bb31b3949 feat(studio): add keyframe track headers 2026-07-27 19:52:06 +02:00
Miguel Angel Simon Sierra fed5e5b71d feat(studio): add timeline property lanes 2026-07-27 19:52:06 +02:00
Miguel Angel Simon Sierra 6e0118cb3f refactor(studio): guard the diamond connector's previous keyframe
CONTRIBUTING.md asks for a guard clause rather than a non-null assertion outside
an already-checked path. The index check and the lookup are now the same guard.
2026-07-27 19:52:05 +02:00
Miguel Angel Simon Sierra 706f537f33 fix(studio): let Escape cancel a keyframe retime and throttle its preview
Escape now ends an in-flight diamond drag the way it already ends clip
and element drags: the armed gesture is marked cancelled, the preview is
dropped, and the pointerup that follows is swallowed instead of falling
through to the click branch.

The preview also flushes once per animation frame instead of once per
pointermove, so a high-rate trackpad no longer re-renders every diamond
in the row several times a frame. Single-diamond retime stays the
documented scope; multi-select drag needs a batched mutation the script
ops do not express yet.
2026-07-27 19:52:05 +02:00
Miguel Angel Simon Sierra e36fb385bc feat(studio): add timeline keyframe retiming interactions 2026-07-27 19:52:05 +02:00
Miguel Angel Simon Sierra 4c7703ff8f refactor(studio): drop the duplicated row-top docblock
The getTimelineRowTop docblock had a second copy sitting on
TimelineTrackHeightClip, where it describes nothing. Only the one on the
function stays.
2026-07-27 19:52:05 +02:00
Miguel Angel Simon Sierra 95213d7353 refactor(studio): give resolveTimelineMove a row-based vertical axis
Rows stopped sharing one pixel height when lanes gained expansion, so the only
production caller was passing cumulative row coordinates with trackHeight 1 and
both scrollTops zeroed. The parameter names described units the values no longer
carried. The vertical axis is now a row index and the caller keeps ownership of
folding scroll and per-row heights into it.
2026-07-27 19:52:05 +02:00
Miguel Angel Simon Sierra d518972f8b feat(studio): add variable timeline timing and layout 2026-07-27 19:52:05 +02:00
Miguel Angel Simon Sierra 521bba6437 refactor(studio): resolve tween selector ids through the shared reader
The local extractIdFromSelector duplicated the `#id`-only regex that
idFromSelector replaced, so both DOM-less paths in
resolveSelectorElementIds (no-iframe fallback and querySelectorAll-throw
recovery) read no id at all for the bracketed `[id="..."]` form writers
emit for CSS-unsafe ids. Deleted the duplicate and imported the shared
reader; both forms now resolve.
2026-07-27 19:51:51 +02:00
Miguel Angel Simon Sierra b386b55f73 fix(studio): clamp the timeline scrub to 0 instead of dropping it
Dragging the playhead to the start of the composition needed a very slow
drag. The scrub surface begins GUTTER + TRACKS_LEFT_PAD px right of the
viewport edge, and both scrub paths bailed out when the pointer sat left of
that origin rather than clamping. So the last 80px of the drag toward zero
silently did nothing: the playhead stuck at whatever the last in-range sample
reported, and only a drag slow enough to sample inside the thin sliver before
the origin ever reached 0.

Both paths now share getTimelineScrubTime, which clamps to [0, duration]. One
owner, so the live-feedback path and the committed-seek path cannot disagree
about the edge again.
2026-07-27 19:09:16 +02:00
Miguel Angel Simon Sierra 3c7400af89 fix(studio): close the review findings in this PR instead of at the stack tip
The R1/R3 residuals on this PR were fixed at the top of the stack, so they
only cleared once every branch above landed. They belong here, next to the
code they correct:

- `idFromSelector` inverts `idSelector` for both regex readers, so the
  post-commit cache refresh stops skipping the CSS-unsafe ids `idSelector`
  exists to support.
- `deduplicateKeyframes` drops `ease` when it is ambiguous; the flag was the
  only honest answer and the last-writer-wins curve belonged to an arbitrary
  colliding tween.
- `isStaticPositionHold` is now the single owner of the hold skip. The
  `sourceAnimations` filter and the `allKeyframes` filter had diverged on
  whether `immediateRender` counts as a property.
- The keyframe-cache setters no-op when the write changes nothing, instead of
  handing every subscriber a fresh Map.
- `reset()` clears `focusedEaseSegment`.
- The test hook `delete`s its window key rather than setting it to undefined,
  so feature detection still works.
- The `toClipKeyframes` fixture uses `as unknown as T` with the justification
  CONTRIBUTING.md asks for.
2026-07-27 19:02:21 +02:00
Miguel Ángel f1c8f56bca Merge pull request #2831 from heygen-com/fix/feedback-plain-events
fix(telemetry): send feedback as plain events, not PostHog surveys
2026-07-27 17:30:56 +02:00
Miguel Angel Simon Sierra 597c14a887 fix(telemetry): send feedback as plain events, not PostHog surveys
CLI and Studio feedback were emitted as `survey sent` with `$survey_*`
properties, so every rating was ingested as a PostHog survey response even
though no survey definition, targeting, or popover backs them.

Emit `cli_render_feedback` and `studio_feedback` with plain `rating` /
`comment` properties instead. Same fields, same call sites, same opt-out.
2026-07-27 16:46:51 +02:00
James Russo 3a0590925c perf(ci): run the two heaviest fixtures in distributed mode (#2825)
* perf(ci): run the two heaviest fixtures in distributed mode

* test(ci): pin distributed-mode fixtures to harness support
2026-07-26 22:42:12 -07:00
James Russo d8a8f8e044 perf(ci): order Dockerfile.test layers so producer changes do not rebuild core (#2822) 2026-07-26 22:11:31 -07:00
James 5cad2bc312 chore: release v0.7.76 v0.7.76 2026-07-27 05:08:40 +00:00
James Russo 28a340aa6d Merge pull request #2823 from heygen-com/fix/plan-v2-sparse-inactive-videos
fix(producer): materialize sparse video directories
2026-07-26 21:59:48 -07:00
Xuanru Li 75ed99e1d4 fix(check): elongated pivot drift + counterfactual connector_detached (#2819) 2026-07-26 21:43:42 -07:00
James ddb59d3567 fix(producer): materialize sparse video directories 2026-07-27 04:22:26 +00:00
James 45b458c007 chore: release v0.7.75 v0.7.75 2026-07-27 03:38:16 +00:00
James Russo c72ba7ee01 Merge pull request #2821 from heygen-com/fix/distributed-beginframe-fallback
fix(producer): fallback distributed capture safely
2026-07-26 20:32:10 -07:00
James 96cafb47c6 fix(producer): fallback distributed capture safely 2026-07-27 03:08:40 +00:00
James Russo 372bead45c Merge pull request #2820 from heygen-com/fix/plan-v2-fixture-shard-schedule
fix(regression): schedule Plan v2 color fixture
2026-07-26 20:04:32 -07:00
James 51cbbe6fc9 fix(regression): schedule plan v2 color fixture 2026-07-27 02:31:46 +00:00
James Russo f67012eb9f ci(regression): compute the shard matrix from recorded fixture timings (#2815)
* ci(regression): compute the shard matrix from recorded fixture timings

* ci(regression): refresh shard timings from a green post-PSNR run

* fix(ci): close two silent-skip holes in the shard schedule contract

* ci(regression): schedule the new static-volume-future-set fixture

* test(producer): regenerate static-volume-future-set golden in the pinned container
2026-07-26 19:16:39 -07:00
James Russo 0cc78c1d42 chore: release v0.7.74 (#2818) v0.7.74 2026-07-26 21:57:54 -04:00
James Russo 58869f0878 fix(producer): accept partial color metadata in plan v2 (#2814)
* fix(producer): accept partial color metadata in plan v2

* test(engine): make partial color probe hermetic

* fix(producer): validate plan v2 sentinels in fallback mode
2026-07-26 21:48:51 -04:00
James Russo 98a4cd70fd perf(producer): compute regression PSNR in one ffmpeg pass (#2813)
* perf(producer): compute regression PSNR in one ffmpeg pass

* fix(producer): fail loudly when one PSNR input runs out of frames
2026-07-26 18:14:53 -07:00
James Russo f03b7e0fcf Merge pull request #2817 from heygen-com/fix/2810-gcp-beginframe-contract
fix(gcp): enforce effective BeginFrame capture
2026-07-26 18:11:53 -07:00
James Russo 6c98e54064 Merge pull request #2816 from heygen-com/fix/2809-static-volume-live-seek
fix(core): avoid live volume probe during render
2026-07-26 18:11:12 -07:00
James 2a284a8e3a fix(gcp): enforce effective BeginFrame capture 2026-07-27 00:02:37 +00:00
James 477defc7e7 fix(core): avoid live volume probe during render 2026-07-26 22:39:58 +00:00
James Russo 2dddb4c463 Merge pull request #2812 from heygen-com/release/v0.7.73
chore: release v0.7.73
v0.7.73
2026-07-26 17:36:13 -04:00
James 527751c871 chore: release v0.7.73 2026-07-26 21:25:03 +00:00
James Russo 8efc47c21c fix(producer): align frame coverage with extraction rounding (#2770)
## What

- model expected video-frame counts using the same rounding contract as the extraction branch:
  - CFR `-vf fps=<fps>`: nearest output-frame boundary
  - VFR `-fps_mode cfr -r <fps>`: ceil
- retain fail-closed ceil behavior when extraction metadata is missing
- keep positive sub-frame clips at a minimum of one expected frame
- preserve the existing 95% truncation gate and source-duration credit

## Why

The coverage gate universally used `ceil(duration * fps)`, but FFmpeg's CFR fps filter rounds to the nearest boundary. This made successfully extracted short CFR clips such as 0.616666s at 30 fps look truncated (18 captured vs 19 expected).

In the dashboard window, 124 of 335 video-coverage failures were exactly one frame short. Historical logs do not include `isVFR`, so that is the maximum addressable cohort rather than a guaranteed reduction. Zero-frame and materially truncated extraction failures remain fail-closed.

## Safety

- VFR still uses ceil and the existing strict coverage threshold.
- Missing extraction metadata still uses ceil.
- No retry, fallback, Temporal workflow, or render-plan behavior changes.
- Intended rollout is through the producer sidecar canary with explicit internal in-process jobs before dev and production promotion.

## Test

- `bunx vitest run packages/producer/src/services/render/videoFrameCoverage.test.ts` (28 passed)
- `bun run --filter @hyperframes/producer typecheck`
- `bunx oxlint ...videoFrameCoverage.ts ...videoFrameCoverage.test.ts`
- `bunx oxfmt --check ...videoFrameCoverage.ts ...videoFrameCoverage.test.ts`
- `bunx fallow audit --base origin/main --fail-on-issues`
- repository pre-commit gates
2026-07-26 17:20:25 -04:00
James f0c2c7d233 fix(producer): align frame coverage with extraction rounding 2026-07-26 20:33:19 +00:00
James Russo f500a42d9e fix(producer): type video extraction failures (#2776)
## Summary
- classify per-source video download/probe/decode/extraction failures with a bounded taxonomy and safe producer-facing summaries
- add candidate-only, at-most-one transient retry with cleanup and retry telemetry
- preserve default engine/producer behavior when the policy is off
- carry allowlisted extraction error codes through blocking JSON and SSE responses

## Stack
Depends on #2774 for atomic remote downloads and its single owned download retry. This PR is intentionally based on `fix/atomic-video-download-retry`; rebase/change the base to `main` after #2774 merges.

## Default compatibility
`HF_VIDEO_EXTRACTION_FAILURE_MODE` defaults to `off` and forces `maxTransientRetries=0`.

With the feature off:
- metadata probe failures keep the legacy Promise rejection
- grouped extraction keeps the existing grouped-to-direct fallback
- no new producer failure gate is enforced
- render-plan schema, Plan v1 artifacts, chunk routing, and distributed execution are unchanged

Typed metadata aggregation is explicit and enabled only by the candidate enforce lane.

## Retry ownership
- remote downloads: exactly one retry owned by #2774
- metadata/FFmpeg extraction: at most one retry only when `HF_VIDEO_EXTRACTION_MAX_RETRIES=1`
- invalid, missing, rejected, out-of-range, zero-output, cancellation, and unknown/internal failures do not retry
- non-finite or invalid runtime retry budgets fail closed to zero
- the superset optimization is never retried; on failure it preserves direct-member fallback, and only the individual ranges can use the bounded retry
- retry counters increment when a retry is scheduled, including exhausted retries

The internal sidecar and Experiment Framework must treat both exhausted stage codes as workflow-terminal after the producer-local budget. Candidate enforcement must not be enabled until those companion mappings are deployed, or Temporal can multiply producer attempts.

## Failure contract
- `VIDEO_SOURCE_UNRENDERABLE`: at least one deterministic/unknown source failure
- `VIDEO_EXTRACTION_FAILED`: all source failures are transient but the producer-local budget is exhausted

Only the allowlisted code and kind/count summaries cross JSON/SSE. Raw diagnostics remain engine-local because they may contain signed URLs or local paths.

## Rollout
1. merge and deploy with stable/candidate both `off`
2. candidate `observe`, retries 0
3. candidate `observe`, retries 1
4. deploy internal + EF terminal transport mappings
5. candidate `enforce`, retries 1
6. keep stable off until success delta, retry counts, extraction latency, CPU/disk, and queue backlog are acceptable

## Validation
- engine focused suites: 105 passed
- producer focused suites: 15 passed
- full engine suite: 1,176 passed, 3 skipped
- full producer unit lane: 32 Vitest files / 393 tests plus all classified Bun unit tests
- engine and producer typechecks passed
- oxlint, oxfmt, Fallow, tracked-artifact, and commit hooks passed
- independent review: approved for merge default-off; candidate enforcement held on companion transport rollout
2026-07-26 16:33:00 -04:00
James 33ca1de063 fix(render): aggregate extraction launch failures 2026-07-26 19:47:33 +00:00
James c01e1a5f96 fix(producer): narrow extraction error shapes honestly 2026-07-26 19:47:33 +00:00
James 9b63646c8a fix(producer): type video extraction failures 2026-07-26 19:47:33 +00:00
James Russo 814f9cd977 fix(engine): make remote video downloads atomic (#2774)
## Summary
- stage remote video downloads in private per-attempt directories and atomically publish only complete non-empty files
- keep the deadline active through response-body streaming and retry one bounded transient failure
- preserve render cancellation without sharing abort ownership across independent callers
- manually follow at most five redirects, validating HTTPS/public-host policy before every hop
- remove stale zero-byte finals and avoid a permanent render-scoped cache map

## Root cause
The previous downloader wrote directly to the final cache path and cleared its timeout as soon as response headers arrived. A body timeout or mid-stream socket reset could therefore leave a truncated file at a path that later extraction treated as complete. The downstream symptom was zero extracted frames followed by a generic video coverage failure.

## Retry policy
Exactly one retry is allowed for 408, 429, 5xx, timeouts, empty successful bodies, and network/socket failures including nested Undici errors. Cancellation, 404/410, other 4xx, URL/redirect validation errors, and filesystem errors are not retried.

## Security and portability
- `mkdtempSync` creates an unguessable same-filesystem staging directory; exclusive writes plus atomic rename prevent symlink planting and partial publication
- redirects use `redirect: "manual"` and every resolved `Location` is revalidated before the next request, blocking redirect-to-private/IMDS bypasses
- the partial file is opened read/write for `fsync`, which preserves flush semantics and avoids Windows `EPERM`

## Rollout safety
This does not change render-plan schema, Plan v1 artifacts, chunk routing, or distributed rendering semantics. It is suitable for the candidate sidecar lane first; stable can remain pinned while we compare video extraction and coverage failures.

## Validation
- focused urlDownloader suite: 28 passed
- full engine suite: 1,169 passed, 3 skipped
- engine typecheck passed
- oxlint and oxfmt passed
- fallow audit passed
- independent code review: approved, no blockers
2026-07-26 15:45:50 -04:00
James 2e84faeb28 fix(engine): block future-use IPv4 downloads 2026-07-26 19:00:41 +00:00
James 4b81f78586 fix(engine): close downloader trust-boundary gaps 2026-07-26 19:00:41 +00:00
James 5ce2eb879d fix(engine): narrow network error shapes honestly 2026-07-26 19:00:41 +00:00
James c01f6b4468 fix(engine): make video downloads atomic and retry transient failures 2026-07-26 19:00:41 +00:00
James Russo a8ee81f3d8 fix(producer): attribute and stop oversized plans early (#2773)
## What

- add an exact, symlink-safe plan-size breakdown with fixed or hashed component labels
- reject oversized plans after the compiled tree stabilizes, before video extraction
- reject again after artifact promotion but before the full freeze/hash read
- retain the exact post-freeze ceiling check and existing `PLAN_TOO_LARGE` contract/message prefix
- remove only freeze-owned stale metadata before the preliminary check when callers reuse a plan directory

## Why

The existing ceiling only runs after a multi-GiB plan has been fully materialized and hashed. Production examples range from roughly 3 GiB to 12.6 GiB. This gives us enough attribution to design Plan v2 from real data while avoiding obviously wasted extraction and hashing work.

## Compatibility and rollout

- no v1 plan artifact or `plan.json` format changes
- no queue, routing, retry, worker-count, or steady-state render changes
- under-limit plan bytes/hashes remain governed by the existing freeze path
- intended for the candidate sidecar and canary lane before broader rollout

This does not interrupt a single FFmpeg extraction once it is running. A canaried extraction-budget monitor is a separate follow-up.

## Validation

- focused distributed-plan tests: 54 passed
- full producer unit lane: 31 Vitest files / 384 tests plus all classified Bun lanes passed
- typecheck, lint, format, Fallow, tracked-artifact checks passed
- independent code review approved
2026-07-26 15:00:14 -04:00
James ebb02cafe7 fix(producer): reset distributed plan scratch state 2026-07-26 18:12:15 +00:00
James d699cbf014 fix(producer): attribute and stop oversized plans early 2026-07-26 18:12:15 +00:00
Ular Kimsanov 5bfda4e087 Merge pull request #2797 from heygen-com/feat/studio-professional-color-grading
feat(studio): add professional grading controls
2026-07-26 10:45:43 -07:00