Commit Graph
2907 Commits
Author SHA1 Message Date
James Russo 1b4cf12cd3 refactor(cli): add stderr diagnostics logger + oxlint guard (#2551) 2026-07-16 09:35:27 -04:00
James Russo d92d1d4f51 fix(cli): keep diagnostics on stderr for --json commands (#2522) 2026-07-16 09:14:39 -04:00
Somansh ReddyandClaude Opus 4.8 5e78a29af3 docs(guides): add Claude Design → Send to HyperFrames single-file import guide (#2131)
* docs(guides): add Claude Design → Send to HyperFrames single-file import guide

Self-contained authoring guide for the 'Send to HyperFrames' MCP import path
(distinct from the download-ZIP flow): single self-contained HTML wire format,
the composition contract the importer validates, inline-brand-assets fidelity
rules (base64 data: URIs), no bundled/splash artifacts, keep-the-original-as-
reference, a complete worked skeleton, the animation-pattern catalog, and
determinism rules. Reviewed for technical accuracy against a known-good
imported+rendered composition.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(send-to guide): assets must be resolvable (inline OR fetchable URL), not inline-only

Large media can exceed the import size cap, so "inline everything" is wrong for it,
and the import tool description now states "resolvable refs." Align the guide: inline
data: URIs preferred (fonts/images/logos — the only fully self-contained form), a
publicly-fetchable absolute URL allowed for large media, and explicitly forbid relative
paths / local-file variables / expiring-or-private hosts (the media-expiry trap).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 04:43:57 -07:00
Vance Ingalls dbb684fa6c Merge pull request #2535 from heygen-com/fix/runtime-partial-timeline-pause-guard
fix(runtime): tolerate registry timelines without pause() in interactive transport
2026-07-16 03:03:22 -07:00
Vance Ingalls 74f52fcd96 Merge pull request #2534 from heygen-com/fix/panel-sections-audio-layout-style-gate
fix(studio): gate Layout and Style panel sections on the element having a rendered box
2026-07-16 02:43:57 -07:00
Vance IngallsandClaude Fable 5 7e0bd401c8 chore(core): regenerate position-edits-render artifact with current esbuild
The committed artifact was generated with an older esbuild than the
current lockfile resolves; CI's check:position-edits-render regen now
produces different (equivalent) minifier variable naming and fails the
diff gate. Regenerate to match — no source change to positionEdits.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 02:17:36 -07:00
Vance Ingalls eb967f1542 Merge pull request #2533 from heygen-com/fix/studio-promotable-badge-overlap
fix(studio): reserve a gutter for the promote-to-variable badge
2026-07-16 02:06:28 -07:00
Vance Ingalls 1a2e7524e2 Merge pull request #2532 from heygen-com/fix/studio-flat-stroke-select
fix(studio): make flat inspector's Stroke width free of style-name typing
2026-07-16 02:06:07 -07:00
Vance Ingalls f96b78165e Merge pull request #2528 from heygen-com/fix/studio-flat-row-values-right-aligned
fix(studio): right-align FlatRow's value input in the flat inspector
2026-07-16 02:05:48 -07:00
James Russo 6a1585df27 refactor(render): centralize execution ownership (#2158) 2026-07-16 05:00:40 -04:00
Vance IngallsandClaude Fable 5 8826d2d71d fix(runtime): tolerate registry timelines without pause() in interactive transport
A window.__timelines entry is authored content and may be a partial
RuntimeTimelineLike (duration/seek only, no pause). Timeline resolution
is deliberately permissive — duration-based — and such compositions
render fine, because the render path only seeks. But every interactive
transport path (play/pause/seek, bind, rebind-tick, boot) called
capturedTimeline.pause() unguarded, crashing studio playback with
'tl.pause is not a function' — the top recurring studio:unhandled_error
in telemetry across versions 0.6.121 through 0.7.59 (~150-175/day).

Guard all pause sites through one helper (typeof check + swallow, plus
a once-per-page timeline_missing_pause analytics event so composition
authors can find the partial timeline), matching the safeVoid pattern
player.ts already uses. In the rebind restore path, pause is guarded
separately so a missing pause() no longer aborts the seek/play restore
behind it in the same try/catch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 01:58:39 -07:00
Vance Ingalls ff26e5f2c8 Merge pull request #2529 from heygen-com/via/resolution-portrait-fix
fix(cli): accept portrait aspects for --resolution alias flag
2026-07-16 01:54:55 -07:00
Vance Ingalls 8a793ba2b2 fix(studio): gate Layout and Style panel sections on the element having a rendered box
Both panels showed Layout (X/Y/W/H/Rotation/Z-index) unconditionally —
no gate existed for it at all — and Style was gated only on
canEditStyles (a permission check), never on the element's tag. Neither
gate accounted for `<audio>`, which never paints a visual frame, so a
music track's inspector showed a full set of position/size/fill/shadow
controls with zero visual effect.

Add `layout`/`style` applicability to resolveEditingSections (core),
keyed on tag !== "audio", and gate both panels' Layout section and the
existing Style gate on it. Media/Motion/Grade/Text were already
correctly gated (verified via a research pass across both panels) and
are untouched.
2026-07-16 01:47:46 -07:00
Vance Ingalls 14dbfca5a7 fix(studio): give flat Text section rows more vertical breathing room
6px between rows left little clearance above a row's value for the
promote-to-variable badge (now positioned above the row). Widen the
row gap to 10px.
2026-07-16 01:17:04 -07:00
Vance Ingalls 5f045b6bdf fix(studio): move the promote-to-variable badge above the row instead of a padding gutter
Shrinking the wrapped control's width to make room for the badge (previous
commit) fixed the overlap but pushed the value left unnecessarily. Move the
badge to sit above the row instead, clearing the value without touching its
layout.
2026-07-16 01:11:30 -07:00
Vance Ingalls 9fa1b26570 fix(studio): reserve a gutter for the promote-to-variable badge
PromotableControl absolutely-positions its "◇ var" / "◆ {id}" badge over
the wrapped control without reserving any space, so on rows where the
value renders flush to the right edge (flat Font/Color rows) the badge
sat directly on top of the value text instead of beside it. Add a
right-padding gutter on the wrapper sized to each badge state, and cap
the bound chip to a fixed max-width so it always fits inside its gutter.
2026-07-16 01:06:11 -07:00
ViaandClaude 2d398ed274 fix(cli): wire aspect-agnostic resolution through cloudrun/lambda/batch + preflight recompute
Addresses R2 CHANGES_REQUESTED from Miga + Rames on PR #2529:

1. Sibling-surface gap (blocker): `hyperframes cloudrun render{,-batch}`,
   `hyperframes lambda render{,-batch}` all advertised the same tier-only
   aliases (`1080p` / `hd` / `4k` / `uhd`) but normalized them to `landscape`
   and never set `outputResolutionAspectAgnostic`. The distributed plumbing
   PR #2529 added received `undefined` from those callers, so portrait `1080p`
   still hit the original aspect-mismatch on Cloud Run / Lambda.

   Fix: introduce `resolveResolutionFlagPair` in `@hyperframes/parsers` (the
   single source of truth for the two-step normalize + aspect-agnostic
   detect) and route every distributed entrypoint through a shared
   `parseOutputResolutionFlag` CLI util so the alias signal now reaches
   `SerializableDistributedRenderConfig`. Studio Server keeps its
   canonical-only HTTP contract; that intent is now pinned in tests.

2. Preflight recompute (hardening): the earlier "downgrade aspect-mismatch"
   preflight cleared un-remapped mismatches, so IG 4:5 (non-preset aspect,
   no sibling) and portrait-4K comp + `--resolution 1080p` (remap +
   downsample) both slipped through to fail late in `resolveDeviceScaleFactor`.
   Now `checkRenderResolutionPreflight` computes the effective preset via
   `suggestMatchingPreset` (mirroring the compile stage's
   `adaptAspectAgnosticResolution`) and re-checks against that — only
   genuinely-fixable mismatches clear early. New tests pin both regressed
   input classes.

3. Docker forwarding boundary test (Miga's important #2): pinned
   `1080p` survives verbatim as `--resolution 1080p` in the Docker args
   so the in-container CLI can re-run `isAspectAgnosticResolutionAlias`.

4. Doc-nit (Miga): parsers/src/types.ts no longer references the
   nonexistent `resolveResolutionForComposition` — points at the actual
   remap helpers.

Fallow: cloudrun.ts / lambda.ts share 390 lines of pre-existing structural
symmetry (parallel AWS + GCP dispatchers), and lambda/render.ts +
render-batch.ts declare parallel RenderArgs interfaces. Both re-flagged
after threading the aspect-agnostic field through each surface; ignored
with justification in .fallowrc.jsonc. lambda.ts's `run` and
lambda/render.ts's `waitForCompletion` are pre-existing CRAP-score
hotspots untouched by this PR — added under health.ignore.

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

— Via
2026-07-16 08:01:56 +00:00
Vance Ingalls d5a259a3d2 fix(studio): make flat inspector's Stroke width free of style-name typing
Stroke width committed border-width and border-style together from one
free-text field, so setting a style meant typing an exact CSS keyword
(e.g. "dashed") with no indication of which ones were valid — the row
also duplicated the discoverable Stroke style select directly below it.
Stroke width now only commits border-width; style changes go exclusively
through the existing select.
2026-07-16 00:48:53 -07:00
Vance Ingalls b32f9a3e86 fix(studio): right-align FlatRow's value input in the flat inspector
FlatRow lays out label…gap…value across a `justify-between` row, but the
shared CommitField input it wraps had no text-align, so its text hugged
the LEFT edge of the value's own (often much wider) right-hand box —
looking left-aligned relative to the row, out of step with FlatSelectRow
and FlatSlider, which already right-align.

Added an optional `align` prop to CommitField (default "left", preserving
the legacy panel's MetricField/DetailField layouts where label-then-value
sits inline and left reads naturally) and pass `align="right"` from
FlatRow. Left the Motion Timing row's Start/End/Duration cells alone —
those stack label-above-value in a grid, a different pattern from the
inline label…value row this fix targets.

New tests: FlatRow's input has `text-right` (not `text-left`); the legacy
MetricField's input keeps `text-left` (not `text-right`), pinning
CommitField's default so the shared component doesn't drift for the
panel that didn't ask for this.

Full studio suite (2645 tests) green; typecheck/oxlint/oxfmt clean.
2026-07-16 00:48:53 -07:00
Vance Ingalls b0bb468877 Merge pull request #2519 from heygen-com/fix/studio-variable-promote-shared-session-sync
fix(studio): resync the shared SDK session after a Design-panel variable promote
2026-07-16 00:36:10 -07:00
Vance Ingalls d0d281de50 Merge pull request #2525 from heygen-com/via/audiopad-ffmpeg8-compat
fix(producer): audioPadTrim FFmpeg-8.x-compatible apad invocation
2026-07-16 00:35:53 -07:00
Vance Ingalls 3e4c761842 Merge pull request #2527 from heygen-com/via/artifact-validated-exit-gate
fix(cli): don't override exit code after artifact validated
2026-07-16 00:35:30 -07:00
Vance Ingalls 9d36bc9d95 Merge pull request #2526 from heygen-com/via/transcribe-timeout
feat(cli): configurable transcribe timeout with duration-scaled default
2026-07-16 00:35:08 -07:00
Vance Ingalls 578d6202b4 fix(studio): resync the shared SDK session after a Design-panel variable promote
Reported as "template variables are broken": binding an element's field to a
variable via the flat inspector's "◇ var" promote chip (or editing an
already-bound field's value) wrote the correct bytes to disk, but the
Variables tab kept showing the pre-edit value until the whole Studio page
was hard-reloaded.

Root cause: DesignPanelPromoteProvider deliberately opens its OWN SDK
session (`useSdkSession(projectId, selection.sourceFile ?? activeCompPath)`)
so that promoting inside a sub-composition binds the variable in the
sub-comp's own file, not the host's. For the common case — a top-level
element, same file as `activeCompPath` — this session is a SEPARATE
in-memory `Composition` instance from the shared one `VariablesPanel`
(Variables tab, Slideshow, etc.) reads. A persist through the promote
provider's session never fires the shared session's own "change" event.

Worse, the shared session's file-change listener runs
`isSelfWriteEcho(path, content)` to decide whether to reload — but
`sdkSelfWriteRegistry` is keyed by file path only, not by session instance
(its own doc comment assumes "the studio process has a single SDK session
lifecycle at a time"). It sees the promote provider's write registered
under the same path and concludes it's its own echo, permanently
suppressing the reload it actually needs.

Threaded `forceReloadSdkSession` (the same mechanism every other
server-side-write path in Studio already uses for exactly this "resync
after a write I didn't make myself" case) from App.tsx through
StudioRightPanel into DesignPanelPromoteProvider, and call it after every
successful promote/setDefault persist — unconditionally, not gated on the
promote target matching activeCompPath, since re-opening a file that
didn't change is a harmless no-op re-parse and a path-equality guard here
already produced one subtly wrong comparison (activeCompPath can be null
while the shared session still defaults to "index.html") before landing on
this simpler version. Verified live: editing a variable-bound field's
value now updates the Variables tab immediately, no reload required.

App.tsx crossed the 600-line file-size gate after threading the new prop;
extracted the tiny handleAddAssetAtPlayhead wrapper into its own
useAddAssetAtPlayhead hook (with a regression test) to bring it back under.

Full studio suite (2639 tests) green against a fresh main; typecheck/
oxlint/oxfmt clean.
2026-07-16 00:18:36 -07:00
ViaandClaude f3b941f1e3 fix(cli): reduce CRAP score by extracting exit-path helpers; bump contactSheet Windows timeout
Fallow flagged the `uncaughtException` arrow at CRAP 30.0 (right at
threshold). Extracting the three exit paths — emitCliErrorEvent,
reportPostRenderTerminationEvent, exitAfterPostRenderTermination,
exitAfterCliFailure — pulls the two `_flushSync?.()` optional-chain
branches out of the arrow body and drops cyclomatic to 3. Same shape
already used on compileStage in a sibling PR. Behavior preserved:
EPIPE → exit(0), renderSucceeded → report + flush + exit(0),
default → commandFailed + track + flush + exit(1) for uncaught;
renderSucceeded → report + return, default → commandFailed + track
for rejection.

Windows contactSheet flake: `createContactSheet > writes PNG output`
timed out at 20025ms (default ceiling 20000ms) on Windows. Test does
milliseconds of real work — the wall-clock overhead is sharp's native
binary fork under runner I/O jitter. PR #2492 already tried lightening
the test; the flake persists at the default ceiling. Bumping the
per-test timeout to 60s absorbs runner jitter without hiding real
slowdowns (a genuine sharp regression would blow through 60s just as
readily as 20s). Targeted per-test bump, not a global suite bump.

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

— Via
2026-07-16 07:08:45 +00:00
ViaandClaude 40f4cfe92f fix(producer): materialize audioPadTrim concat script to real file
The prior fix (dc410ca) dropped `pathToFileURL` from the pad-concat step
to make FFmpeg 8.x on Windows stop rejecting `file:///C:/…` URLs — but
kept feeding the concat script via `pipe:0` stdin. That combination
broke Linux CI: FFmpeg's concat demuxer resolves bare paths in the
script against the base URL of the script's own source, and when the
script is fed via `pipe:0` the base URL is `pipe:`. Absolute POSIX
paths (`/tmp/foo.aac`) then join to `pipe:/tmp/foo.aac`, which the
demuxer tries to open as a pipe and fails with:

    [concat @ 0x…] Impossible to open 'pipe:/tmp/…/audio.aac'
    pipe:0: End of file

Manually reproduced with `ffmpeg-static@7.0.2` on this repo's binary.

Fix: write the concat script to a real temp file (`<outputPath>.concat-
list.txt`) and pass `-i concatListPath` — matching the sibling concat
in `distributed/assemble.ts:180-186` exactly. A real file's directory
becomes the base URL, so absolute paths in the script resolve as-is on
both Linux and Windows. The `file://` scheme prefix stays out of the
script (Windows FFmpeg 8.x fix preserved) and no `pipe:` prefix gets
prepended (Linux regression fixed). Cleanup path list now covers both
the silence tail and the concat list script.

Also drops the now-unused `runFfmpegWithStdin` helper — no consumer
needs stdin plumbing anymore.

Regression pins in `audioPadTrim.test.ts`:
  - `does not emit file:// URLs …` — Windows arg-shape pin (unchanged
    intent, moved from `stdin` to `concatListContent` field).
  - `materializes the pad-concat script to a real file …` — new pin
    that asserts `-i` is not `pipe:0` and points at the concat list
    path, so the Linux failure mode can't regress.

CI failures fixed:
  - CI / Producer: integration tests (assemble.test.ts pad case)
  - regression / regression-shards shard-1 (style-3-prod field-signal
    end-to-end render exercising the assemble pad path)

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

— Via
2026-07-16 06:54:07 +00:00
ViaandClaude 7e58d050f8 fix(fallow): resolve audit findings for portrait --resolution PR
Three findings, all resolved:

- packages/producer/src/server.ts `render` (CRAP 31.6, cyclo 10 — minor):
  pre-existing complexity; the PR only threads
  `outputResolutionAspectAgnostic` through parseRenderOverrides /
  RenderInput and does not touch `render`. Line-shift fingerprint —
  exempted via health.ignore with justification comment.

- packages/producer/src/services/distributed/plan.ts `plan` (CRAP 36.7,
  cyclo 33 — major): pre-existing complexity; the PR only adds one
  optional field spread inside `plan` and does not add branches.
  Line-shift fingerprint — exempted via health.ignore with justification.

- packages/producer/src/services/render/stages/compileStage.ts
  `runCompileStage` (cyclo 23, cognitive 19 — minor): this one is a
  real complexity bump from the two-branch aspect-agnostic re-target
  block added in the fix. Extracted the block into a local helper
  `adaptAspectAgnosticResolution` so `runCompileStage` stays under both
  the cyclomatic (20) and cognitive (15) thresholds.

Verified locally with `fallow audit --base origin/main --fail-on-issues`
(exit 0, "No GitHub PR/MR findings") and `tsc --noEmit` on the producer
package.

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

— Via
2026-07-16 06:52:11 +00:00
James Russo 21cb722ebd refactor(core): unify composition contract (#2157)
* refactor(core): unify composition contract

* fix(parsers): parse start expressions linearly
2026-07-16 02:44:22 -04:00
ViaandClaude 46e9ecf3f2 fix(cli): accept portrait aspects for --resolution alias flag
The aspect-agnostic resolution aliases (`--resolution 1080p` / `hd` / `4k` / `uhd`) previously all normalized to a landscape preset, which rejected portrait 1080x1920 compositions with 'Output resolution incompatible'. Users had to specify the orientation-bearing alias (`1080p-portrait`) or render at native.

This threads two new fields (`outputResolutionAspectAgnostic` + `outputResolutionRaw`) through the render pipeline. At the CLI layer we detect whether the user's flag was an aspect-agnostic alias; at the compile stage we re-map the preset to the composition's orientation via the existing `suggestMatchingPreset` sibling-lookup (formerly private). Explicit orientation-bearing aliases and canonical presets stay strict.

Field signal: ts=1784176662 (darwin/arm64, CLI 0.7.59, `--resolution 1080p` on a 1080x1920 portrait comp).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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

— Via
2026-07-16 06:23:14 +00:00
ViaandClaude 25a04d7fc8 fix(cli): don't override exit code after artifact validated
The render command's post-artifact-validated cleanup (telemetry flush,
feedback prompt, worker/browser teardown, stray promise rejections) can
throw AFTER the producer has committed a valid MP4 to disk. Field signal
ts=1784169760, ts=1784171150, ts=1784172467 (all win32/x64, CLI 0.7.58,
ffmpeg=no, 1080x1920): ffprobe + visual QA confirmed the outputs are
valid, but the CLI exited 1 after the terminal "artifact validated" log
with no final error message.

Introduce a `renderSucceeded` sentinel that flips after `executeRenderJob`
(or the Docker child render) resolves cleanly. From that point on:

  - Post-render steps in the render command (trackRenderMetrics,
    printRenderComplete, warnIfWebmAlphaDropped, maybePromptRenderFeedback)
    run through `runPostRenderStep`/`runPostRenderStepAsync` guards that
    swallow throws, log a compact warning to stderr, and sanitize a stray
    `process.exitCode` back to 0.

  - The CLI's top-level `uncaughtException` handler logs the throw for
    diagnosis but exits 0 instead of 1 when the render already succeeded.

  - The CLI's `unhandledRejection` handler stops flipping `commandFailed`
    (which drove the success:false telemetry field) when the render
    already succeeded.

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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

— Via
2026-07-16 05:56:35 +00:00
ViaandClaude Opus 4.7 f8210d96da feat(cli): configurable transcribe timeout with duration-scaled default
Adds a `--timeout <ms>` CLI flag (and `HYPERFRAMES_TRANSCRIBE_TIMEOUT_MS`
env var) plus a model-slowdown factor in the auto-scaled default so
`hyperframes transcribe` doesn't hard-fail with `spawnSync ETIMEDOUT`
on slow CPUs running heavier whisper models.

Field-signal ts=1784165471 (win32/arm64 emulating x64 on Snapdragon,
CLI 0.7.59) reported the failure on a 63s wav with `-m medium` at ~13x
realtime — the historical 10x-realtime scale (PR #2463) gave 10.5 min
while the machine needed ~13.7 min. Splitting audio and merging offsets
was the manual workaround.

- Add `--timeout <ms>` and `HYPERFRAMES_TRANSCRIBE_TIMEOUT_MS` (min 5000).
  Explicit override bypasses auto-scaling; still capped at 12h.
- Add per-model slowdown factor (tiny 0.5, base 0.7, small 1, medium 2,
  large 4, large-v3-turbo 2). Multiplied into the 10s/audio-second
  baseline so medium/large get proportional headroom while `small.en`
  (the default) preserves the historical safety window.
- Wrap whisper's spawn error with a discoverability hint naming
  `--timeout`, the env var, and the effective timeout when the child
  was killed by SIGTERM/ETIMEDOUT (mirrors PR #2504 protocol-timeout).
- Docs: new `--timeout` row in `docs/packages/cli.mdx` Flags table.

Regression coverage in `packages/cli/src/whisper/transcribe.test.ts`
(56 tests) and `packages/cli/src/commands/transcribe.test.ts` (5 tests):
- Model factor per known name + case-insensitive + safe unknown fallback.
- 63s field-signal case on medium.en → 1_260_000ms (was 630_000ms).
- Explicit override honored below the auto floor + capped at 12h.
- Model factor ignored when overrideMs is set.
- SIGTERM/ETIMEDOUT detection + augmented message contract.
- CLI rejects below-minimum `--timeout` with error naming both the flag
  and the 5000ms floor.

— Via

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-16 05:49:46 +00:00
ViaandClaude dc410ca990 fix(producer): audioPadTrim FFmpeg-8.x-compatible apad invocation
The `audioPadTrim` module's pad-concat step generates a concat script
whose file directives use `file://` URLs (built via Node's
`pathToFileURL`). FFmpeg 8.x on Windows rejects these with
"Impossible to open file:///C:/…" — its `file:` protocol handler strips
the scheme leaving `///C:/…`, which Windows path parsing then rejects.

Field-signal (4 reports over ~24h, all win32/x64, CLI 0.7.59):
  - ts=1784169914 (Baoyu, 60s render, native audio assembly failed)
  - ts=1784177061 (andre 22cores, 345.87s composition, 9 WAV audio elements)
  - ts=1784177375 (KEY DIAGNOSTIC: 13 mono 44.1kHz mp3 tracks, ffmpeg
    8.1.1-full_build gyan.dev, "same project rendered fine in July with
    an older ffmpeg"; manual `ffmpeg -i track.mp3 -af apad=whole_dur=16
    -t 16 -c:a aac out.aac` works with the same binary, so the tool's
    audioPadTrim invocation is the incompatible part)
  - ts=1784177375 (duplicate reporter follow-up)

The concat approach itself is fine — the sibling concat scripts in
`assemble.ts` and `chunkEncoder.ts` pass raw paths (no `pathToFileURL`)
and work on Windows. `audioPadTrim.ts` was the outlier introduced in
PR #1615 (2026-06-20). Aligns with the codebase convention.

Regression pin: unit test asserts the pad-concat stdin never contains
the `file://` scheme, including for a Windows-shaped input path.

End-to-end verification requires a Windows + FFmpeg 8.x reviewer; the
unit test snapshots the arg shape.

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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

— Via
2026-07-16 05:46:56 +00:00
Miguel Ángel 9ed255c0ef Merge pull request #2524 from heygen-com/release/v0.7.60
chore: release v0.7.60
v0.7.60
2026-07-16 01:32:57 -04:00
Miguel Ángel 0f287ee0b6 chore: release v0.7.60 2026-07-16 05:26:57 +00:00
Miguel Ángel 160f142d15 Merge pull request #2521 from heygen-com/fix/stale-compositor-layer
fix(renderer): prevent stale SwiftShader layers
2026-07-16 01:23:59 -04:00
Miguel Ángel 9c25e27da6 docs(renderer): link SwiftShader workaround tracker 2026-07-16 05:19:00 +00:00
Miguel Ángel 54a3ef2000 fix(renderer): prevent stale SwiftShader layers 2026-07-16 04:56:36 +00:00
James Russo b179c95362 fix(engine): emit SystemMemory cgroup notice to stderr, not stdout (#2520) 2026-07-16 00:36:25 -04:00
Vance Ingalls a10e462d02 Merge pull request #2518 from heygen-com/fix/studio-flat-input-affordance
fix(studio): give flat-inspector value fields a resting-state input affordance
2026-07-15 21:24:13 -07:00
Vance Ingalls 399438b963 Merge pull request #2497 from heygen-com/fix/studio-flat-layers-panel-standalone
fix(studio): show Layers full-height in the flat inspector, not split with Design
2026-07-15 21:19:29 -07:00
Vance Ingalls 2b51c52634 fix(studio): give flat-inspector value fields a resting-state input affordance
Every editable value in the flat inspector (FlatRow's CommitField, the
Motion Timing row's Start/End/Duration cells, and every raw <select> —
Style/Text dropdowns, Grade's Custom LUT and Copy-grade-to scope) rendered
its underline/border only on hover (`border-transparent
group-hover:border-...` or no border at all). At rest a value looked like
plain static text, with nothing distinguishing it from a label — testers
reported not being able to tell which fields were editable.

Give each a dim-but-visible resting border (`border-panel-border-input/50`,
or `border-panel-accent/30` for the explicitCustom tier) that brightens on
hover/focus, instead of a fully transparent one. Purely visual — no
behavior change.

Full studio suite (2641 tests) green; typecheck/oxlint/oxfmt clean.
2026-07-15 20:54:07 -07:00
Vance Ingalls fb24ecac22 fix(studio): make setRightPanelTab itself flat-aware, not just the direct tab click
Review feedback on #2497 (Rames D Jusso) found a real gap: the exclusivity
this PR introduced only applied to the direct in-panel tab click, which
calls setExclusiveRightInspectorPane. Every OTHER caller that reaches
setRightPanelTab("design"|"layers") — element select (useDomSelection.ts),
closing block-params (App.tsx), the header Inspector button
(StudioHeader.tsx), and even this PR's own "!inspectorTabActive" entry
branch in handleInspectorPaneButtonClick — went through
trackedSetRightPanelTab's old unconditional additive
`{...panes, [tab]: true}`, reproducing the exact "both tabs highlight, only
one renders" bug this PR claims to fix. Confirmed via the reviewer's traced
repro: fresh boot, click Layers tab while no inspector tab is yet active →
rightInspectorPanes ends up {design:true, layers:true}.

Fixed at the reviewer's preferred choke point: trackedSetRightPanelTab
itself is now flat-aware, applying the same exclusive-radio update
setExclusiveRightInspectorPane does whenever STUDIO_FLAT_INSPECTOR_ENABLED
is on, falling back to the legacy additive update otherwise. This closes
the gap for every current and future caller of setRightPanelTab, not just
the one call site this PR touched.

New usePanelLayout.test.ts cases pin both directions: setRightPanelTab
stays additive under flat=off (legacy split-view behavior unchanged), and
enforces exclusivity under flat=on even when called directly (not through
the tab-click handler) — using the vi.doMock(manualEditingAvailability)
pattern already established in PropertyPanel.test.tsx for flag-dependent
module state.

Full studio suite (2643 tests) green; typecheck/oxlint/oxfmt clean.
2026-07-15 20:53:47 -07:00
Miguel Ángel 33a4f82dca Merge pull request #2516 from heygen-com/fix/2514-hold-last-video-frame
fix(video): hold final frame through composition
2026-07-15 22:58:16 -04:00
Miguel Ángel 4cbde38301 test(producer): use intrinsic duration in style fixtures 2026-07-16 02:15:02 +00:00
Miguel Ángel 1284703adb test(producer): update remaining video slot goldens 2026-07-16 02:15:01 +00:00
Miguel Ángel 31cc2fe875 test(producer): update video slot regression golden 2026-07-16 02:15:01 +00:00
Miguel Ángel 2e8f871bc8 fix(video): hold final frame through composition 2026-07-16 02:15:01 +00:00
Vance Ingalls 71d7be7921 Merge pull request #2501 from heygen-com/task3-position-edit-force-reset
feat(core): applyPositionEdits gains force option and undo reset path
2026-07-15 19:07:05 -07:00
Vance Ingalls 79b688f204 fix(studio): show Layers full-height in the flat inspector, not split with Design
The flat inspector split Layers and Design into a vertically-resizable
stacked pair whenever both panes were toggled on, mirroring the legacy
panel's layout. For the flat redesign this reads as two competing panels
crammed into one column; Layers should always render full-height by
itself there instead.

Gate the split-view branch behind !STUDIO_FLAT_INSPECTOR_ENABLED so it
still applies to the legacy panel, and fall through to Layers rendering
alone (the existing `layersPaneOpen` branch already does this — it just
never got reached previously because the split check ran first).

Also added setExclusiveRightInspectorPane (radio-style: selecting one pane
turns the other off) and use it for the Design/Layers tab clicks under the
flat flag, since leaving both panes independently toggleable would highlight
both tabs as "active" while only one actually renders.

New usePanelLayout.test.ts covers both the existing toggle behavior and the
new exclusive variant. Full studio suite (2634 tests) green; typecheck/
oxlint/oxfmt clean.
2026-07-15 19:05:38 -07:00
Vance IngallsandClaude Fable 5 4ac7b4fa82 fix(core): guard undefined NodeList index in applyPositionEdits loops
tsc (noUncheckedIndexedAccess) types marked[i]/orphaned[i] as
Element | undefined; vitest passed but bun run build failed. Narrow
before the isStylable predicate and regenerate the render-inline IIFE.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 18:30:55 -07:00