* fix(player): bound the parent audio proxy to its clip window
When iframe autoplay is blocked, audible playback is promoted to a parent-frame
audio proxy. The proxy read the clip's data-start/data-duration once at adopt
time and mirrorTime() only skipped (never paused) the element outside that
window — so a trimmed/moved music clip kept playing the full source past its
on-timeline end, even though the iframe element was correctly paused.
Fix: the proxy keeps a reference to its source iframe element and re-reads
data-start/data-duration each mirror tick (live trims/moves apply), pauses the
proxy when the playhead leaves [start, start+duration), and resumes it when the
playhead re-enters during parent-owned playback.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
* fix(core,studio): bound trimmed audio playback to the clip window
Trimmed audio played to the source file's natural end instead of
stopping at the clip edge, on every audio path:
- WebAudio (the audible path in Studio): schedulePlayback now passes
the clip's data-duration as the third start() arg, so the decoded
buffer stops at the trimmed edge instead of running to the file end.
- Runtime element gating: the duration resolver caps each clip by its
own data-duration (min of source length, host window, authored
duration), so a trimmed <audio>/<video> element pauses at its edge.
Studio trim UX:
- Resize live-patches the media-start/playback-start offset, so a
start-edge drag trims into the source instead of only repositioning
the clip.
- AudioWaveform windows the rendered peaks to the trimmed slice so the
waveform tracks the clip edges.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
* fix(player,core): gate proxy playback to the live clip window
Review follow-ups on the parent-audio-proxy / WebAudio bound:
- seekAll now re-reads live source bounds (_refreshEntryBounds) before
gating, so a paused scrub right after a trim/move uses the current clip
window instead of the adopt-time one.
- playAll and clip adoption only start a proxy when the playhead is inside
the clip's window (_playEntryIfActive), so bulk starts / promotion no
longer blip audio for clips outside their window until the next tick.
- The WebAudio buffer is now bounded by the host-composition window too
(matching resolveDurationSeconds), so a sub-composition-nested clip stops
at the same edge on the WebAudio and HTMLMedia paths.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
* fix(core,player): reschedule bounded WebAudio on rate change; guard NaN bounds
A bounded WebAudio source's wall-clock length is baked into start()'s duration
arg (in buffer-sample seconds) at its scheduling rate. Mutating playbackRate in
place on a later rate change does not rescale that bound, so a trimmed clip ends
early (fast) or late (slow). setRate now reports whether the rate changed and
exposes hasBoundedActiveSources(); the runtime stopAll()+reschedules active
clips at the new rate when any bounded source is live. The per-clip schedule
loop is extracted to a shared closure so play() and the rate path agree.
Also guard _refreshEntryBounds against a non-numeric duration attribute parsing
to NaN, which would make every window check false and let the proxy play past
its clip end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
---------
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
* refactor: split useTimelinePlayer.ts and hyperframes-player.ts into focused modules (<500 LOC each)
* fix(ci): scope 500 LOC check to packages/studio, add allowlist for grandfathered files
* feat(cli): Linux ARM64 support — auto-install Chromium on DGX Spark / GB10 / Jetson
Chrome Headless Shell has no Linux ARM64 binary. On arm64 Linux:
- Detects the platform automatically
- Tries to auto-install system Chromium via apt-get (works on Ubuntu/Debian ARM)
- Falls back to clear manual instructions with exact commands
- 'hyperframes browser ensure' guides through the setup interactively
- After setup, all render commands work without any flags
* fix(ci): disable Windows Defender real-time monitoring to prevent EPERM builds
Path exclusions are insufficient — Defender re-scans new files created
during bun install before the exclusion takes effect. Disable real-time
monitoring for the entire job duration instead (standard CI practice).
* refactor(studio): split all files >500 LOC + extract useToast, delete allowlist
All 11 large files split into focused modules under 500 LOC.
App.tsx extracted toast logic into useToast hook (493 LOC now).
.filesize-allowlist deleted — no longer needed.
* fix: remove unused imports from split files, extract useToast from App.tsx
App.tsx: 504 → 493 lines (toast logic extracted to useToast hook)
timelineDOM.ts: remove unused imports from re-export pattern
MotionPanel.tsx: remove unused clampStudioCustomEasePoints import
studioMotionOps.ts: remove unused StudioGsapMotionDirection import
* fix(ci): use Set-MpPreference to fully disable Windows Defender (both jobs)
* fix(producer): use node --experimental-strip-types instead of tsx for build:fonts
Eliminates the tsx binary dependency that Windows Defender locks during
bun install, causing EPERM errors. Node 22.6+ strips TypeScript types
natively with no external binary.
* chore: remove .filesize-allowlist — App.tsx is now 493 lines (<500)
* fix(ci): disable Windows Defender before checkout to prevent all EPERM races
* fix(producer): skip build:fonts if fontData.generated.ts already exists
The generated file is tracked in git, so CI doesn't need to regenerate
it. This avoids @fontsource/inter node_modules access on Windows which
triggers EPERM from Defender scanning during bun install.