* fix(cli): bound checks of non-finishing compositions
* fix(cli): account for cold startup in deadline witness
* fix(cli): wait for profile owners before test cleanup
* test(cli): separate portable deadline and Linux process witnesses
* test(cli): preserve per-launch browser profile ownership
* fix(cli): start check deadline after browser readiness
* chore: release v0.8.152
* docs(changelog): correct the v0.8.152 release notes
The summary overstated snapshot retention, history turns, and video state.
It also left out the new artifact paths and nullable keyframe times.
* feat(cli): save check, snapshot and render artifacts per revision
* fix(cli): keep clean --snapshots inside the project
A symlinked .hyperframes or artifacts folder was followed, so the command
deleted snapshot and check files outside the project.
* test(cli): keep check, snapshot, and render successful when recording fails
A held history or a corrupt output registry warns and leaves the command result alone.
* fix(cli): drop snapshot records when a replaced run is not recorded
A reused --output rewrites the files even when history is busy.
Those records are removed so the old revision does not keep the new pixels.
The preview checkpoint stops waiting after five seconds.
A bad PNG header no longer fails the snapshot.
* fix(studio-server): keep the video-state comment share from rising
The forget helper's doc comment pushed the file above its base share.
* fix(cli): post the preview checkpoint and keep cleanup inside artifacts
A preview checkpoint with no body was sent as GET, so Studio never ran it.
Cleanup now requires the real path to stay under the artifacts folder.
A failed follow-up drop does not warn twice.
The studio-server unpacked budget covers the record-dropping helper.
* docs(cli): shorten the snapshot skill notes
The command examples already show range and composition.
The prose only keeps the output path and the flags that are easy to misuse.
* chore: leave package headroom for artifact cleanup
* test(cli): compare artifact paths portably
* style(cli): format portable artifact assertion
* feat(studio): record video state, edit targets and artifact cleanup
* fix(studio): clear video-state CI gates
* fix(studio-server): pin cleanup reads to file descriptors
* chore: account for cleanup descriptor code in package budget
* build(inspector): publish @hyperframes/inspector to npm
* fix(inspector): drop prepack so pnpm pack --json stays parseable
* fix(inspector): add .js to published declaration imports for NodeNext
* fix(inspector): declare @hyperframes/parsers, which the published types import
* chore(fallow): ignore @hyperframes/parsers, which inspector's published types import
* fix(keyframes): report unresolved GSAP timing instead of guessing rest poses
* fix(keyframes): drop later times when a delay cannot be resolved
Boolean(null) treated an unreadable delay as no delay,
so the next cursor-relative tween kept a guessed start.
* fix(keyframes): keep a staggered rest pose in Studio
The parser stops are what Studio holds, so a staggered from keeps its rest
pose there. The CLI reports that pose as unresolved, and an unread position
no longer seeds the next tween at zero.
onnxruntime's 1DS telemetry, on by default in its macOS wheels, can abort the
Python process at exit when an upload response races teardown. execFileSync
then throws for a line whose WAV was already written, and the audio skill drops
it as non-fatal.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(cli): guard history turns with --exclusive, --who and --if-open
* fix(cli): make exclusive history turns atomic
Re-read the open turn only after acquiring history ownership so
concurrent exclusive begins cannot both claim the same project.
Cover the race with a deterministic two-process regression test.
* fix(cli): refuse a second exclusive turn while a preview is open
The preview's begin window now rejects another agent's live turn, and an
idle turn no longer blocks exclusive begin. The two-process test waits on
pids collected from claim drafts instead of seeing both files at once.
* fix(core): a render keeps a fromTo's from-values when a frame lands just after its start
The frame redraw steps to 1 ms below the frame and back. When a tween starts
inside that 1 ms, the step down reverts its from-values and the step up does
not reapply them, so a wipe bar whose opacity comes only from its from-vars was
never drawn. Reapply them for every tween whose start the step crossed, as
#5125 does for a tween starting exactly on the frame.
Adds the gsap-redraw-start-and-overlap producer fixture, which also pins that a
slow drift resumes after a short move on the same property, as in playback.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(core): keep the redraw's comment share and schedule the new fixture
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(cli): HF_TOOL_CONTEXT artifact events and project lock for agent harnesses
* fix(cli): serialize stale project lock takeover
Use a short-lived break lock so only one waiter can remove a dead
holder's lock. Cover the contention path with six real processes.
* fix(cli): hold snapshot lock through finalization
Keep the project lock while contact sheets, artifact events, and frame
descriptions read captured files so another snapshot cannot replace them.
* fix(cli): recover a project lock after the breaker crashes
A killed takeover left project.lock.break in place, so later commands
waited out the timeout even though both holders were dead. Reclaim that
file only when its recorded process is dead, and compare artifact paths
after the platform resolves them.
* fix(cli): accept a killed breaker process with no signal name
Windows ends the process without reporting SIGKILL, so the regression
was failing before it checked that the leftover breaker can be reclaimed.
* fix(cli): leave a killed project-lock breaker in place
Reclaiming a dead breaker unlinks a name another process may just have
created, so two commands can hold the lock. The timeout names that file
and its pid and says to delete it to continue.
* feat(cli): scope check to a time range or a set of components
* fix(cli): keep ranged check gates truthful
Refuse ranged caption and frame gates when none of their seek times
falls inside the requested range. Make ranged motion reporting,
overlap timestamps, filtering tests, and scope documentation match.
A GSAP tween that outlives a later tween on the same target and property by a
moment takes the property back for that tail and leaves its own end value on
screen. A banner word whose drift ended 20 ms after its exit stayed in the
middle of the frame for the rest of the scene, in playback and in renders.
hyperframes check now reads every tween off the live timelines and warns on
that shape (tail up to 0.25 s), with the fix: end the earlier tween first, or
add overwrite: "auto" to the later one. Longer tails, like a slow drift that
resumes after a short move, are left alone.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(inspector): element map, live inspection engine and Node runtime
* fix(inspector): reject preview reload races
Return the live document generation from the same page invocation as each
query and reject mismatches. Rearm browser cleanup for failures before a
page operation starts, and cover reloads across all three live operations.
* feat(inspector): query timelines and inspect the live preview
* fix(inspector): align live timeline contracts
Keep realm-safe target detection through the restack and align
floating-point interval boundaries. Update the live group fixture and
make the private runtime entry build itself before use.
* fix(inspector): declare generated build inputs
* fix(producer): stop JPEG frame capture adding colour noise to graded footage
Standard and high renders captured page frames as JPEG at quality 95 (the
probe session and distributed chunks at 80). Chrome's encoder turns graded
colour and grain into 8x8 chroma blotches at those settings, and they survive
the final encode. Capture now uses quality 100 for standard and high, from one
owner that the in-process render, the probe session and distributed chunks
all read. Draft keeps 80. The disk estimate for JPEG frames doubles to match
the larger files.
* fix(producer): key resume and disk estimate on the capture quality
Segment resume now fingerprints the capture jpeg quality, so a resume
across a quality change re-renders instead of splicing old-quality
segments. The disk estimate allows for larger frames at any jpeg quality
above draft's 80 instead of only at exactly 100.
* fix(producer): keep segment manifest comment share at base
* fix(producer): fit capture quality comment in four lines
A sub-composition that links a shared stylesheet with a plain path like
assets/fonts.css was only looked up next to its own HTML file, so lint
reported font_family_without_font_face for fonts that stylesheet declares.
The ../ spelling is rejected by invalid_parent_traversal_in_asset_path, so
no spelling passed.
Lint still looks next to the sub-composition first and now falls back to
the project root, which is where the preview and the bundler load the link
from. Links that start with / or contain a .. segment keep their old lookup.
A folder with the stylesheet's name is skipped instead of throwing EISDIR.
Fixes#5074
* fix(engine): encode and tag ProRes mov output as BT.709
* test(engine): read the ProRes colour tags from the first frame
* test(engine): end the ProRes tag probe's options with --
Move the keyframe rest-value table into @hyperframes/core/keyframe-defaults
and import it from the SDK backfill and Studio's gsapShared, replacing the two
hand-synced copies.
Closes#5387
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(media-use): keep frame precision on transcript-cut seek boundaries
Fixes#5416.
A kept range whose boundary sits on a frame got rounded to the millisecond
twice on its way to ffmpeg, and at 29.97/30/60 fps that is enough to land just
past the frame: frame 386 at 30 fps is 12.866666 s, and -ss 12.867 starts that
segment on frame 387 instead. ffmpeg's accurate seek drops the frame, so the cut
came out a frame short from that join on and the picture drifted behind the
sound.
cutlist.mjs rounded the emitted range bounds to 3 decimals, so fmt() in
transcript-cut.mjs never saw more precision than that in the first place; fixing
only fmt() leaves the bug in place. Both now keep frame precision -- 6 decimals
in the cut list, full precision for the -ss/-to actually handed to ffmpeg --
while the 3-decimal fmt() is kept for the times that are only printed.
Measured at 30 fps: a cut of 0-1,1.0666666-3 produced 87 frames before and 88
after (frames 0-29 plus frames 32-89), with no timestamp gap at the join. 25 fps
is unaffected, as the issue notes.
* test(media-use): end ffprobe options with -- in transcript-cut test
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: James <james.russo@heygen.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(media-use): honor telemetryEnabled: false from shared config in optedOut
The CLI's 'hyperframes telemetry disable' writes telemetryEnabled: false to ~/.hyperframes/config.json, but the media-use scripts' optedOut() only checked env vars, so transcribe.mjs, transcript-cut.mjs and audio-duck.mjs kept sending PostHog events for users who had opted out through the CLI.
optedOut() now also returns true when the shared config has telemetryEnabled === false, reusing the readSharedConfig() helper already in the same file. Applied byte-identically to both copies of telemetry.mjs (skills/media-use/scripts/lib and packages/cli/src/media-use/lib, per the copy-parity rule), with tests in both test files.
Closes#5465
* test(media-use): sandbox HOME in the telemetry opt-out test and regenerate the skills manifest
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: James <james.russo@heygen.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The WAAPI and CSS adapters wrote currentTime and then called pause().
On a running animation pause() is pending until the next animation
frame, and Chromium then sets the hold time to the time at that frame,
16.7 ms past the sought time. Pause first: writing currentTime while a
pause is pending completes it with the sought time.
An animation created after the runtime's discover seek is still running
when a render worker makes its first seek, so the first frame of each
worker chunk after the first was drawn 16.7 ms late.
* fix(producer): export plays a split or trimmed scene's media from its in-point
A sub-composition host with a non-zero data-playback-start now shifts and cuts
the video and audio it holds in render, matching Studio preview. Hosts without
an in-point resolve exactly as before.
* fix(producer): export finds in-point scenes in projects without scene files
Media in a document with no inlined composition files carry no render id, so
the collector never looked up their host and ignored an inline host's in-point.
Key host windows on the id the parsers report, and cut a video's head the same
way as audio so nothing plays before the host starts.
* refactor(producer): drop dead no-slot branch in media window and tighten comments
* fix(producer): export starts an inline scene's media at the scene's start
A scene written inline in index.html (no scene file, no in-point) with a
start after 0 exported its video and audio from 0 s. Every composition
host now shifts its media, as the preview's start resolver does; the root
composition still does not cut media at its end.
Also from review: a looping video in an in-point scene stays inside the
scene's slot, and a media id only takes the window of an element the
parsers report, never displacing a render id.
* docs(producer): media collector header states the id rule without history
* fix(producer): a trimmed clip in a split scene keeps picture and sound in sync when export probes
The browser probe compared the page's start point against the value the media
collector had already moved by the scene's in-point, so it reset the picture to
the clip's own trim while its sound track kept the in-point. It now compares
against the start point written in the markup, moves the collected value by
whatever the page changed, and moves a video's sound track with its picture.
* fix(producer): export no longer replays a split scene's cut-away clips when it probes
The media collector drops clips a scene's in-point cuts away entirely (and
clips that start after their scene ends). When the render probed the page,
the probe saw those clips in the DOM and added them back at their
scene-local time, so a split scene's audio played twice. The collector now
reports the ids it dropped and the probe does not re-add them.
An <audio id="v-audio"> start change no longer also moves video v's sound
track, which reports the same id.
* docs(producer): shorten the media collector header to the id rule
* refactor(producer): place each audio track in its own helper
The offline render now pads the source with silence for the whole render
(clip + chain tail + latency) instead of only the true-peak lookahead, so the
pitch shifter's ring drains instead of cutting off about 90 ms early.
A bitcrush node built part-way into a clip seeded its hold counter but started
its held value at 0, playing up to samples-1 frames of silence on a preview
seek. The held value is now primed from the first input sample; render windows
still line up with a whole render.
* fix(core): a scene rendered on its own takes its master chain from its own root
The standalone wrapper for a scene file reused the project index's root
attributes, so the index's data-fx-chain became the master bus. The wrapper
now carries the scene root's own chain, or none when the scene declares none,
as the audio-effects reference describes.
— Jerrai
* fix(engine): run long audio chains window by window instead of reading the file whole
applyAudioFxChain read the whole input WAV into memory, which Node refuses past
2 GiB (a stereo float mix beyond about 93 minutes), and the browser page then
held the track twice. Inputs above 512 MiB of samples are now read, rendered
and written in 5 minute windows, each with 10 s of preceding and 1 s of
following audio rendered and discarded so stateful effects arrive warm. The
output header is patched at the end. Inputs below the threshold keep the
existing whole-file path.
Parameter automation is clocked from the file start and is not windowed. An
input past 2 GiB that carries automation now fails with an AudioFxRenderError
naming that, instead of Node's raw file-size error.
— Jerrai
* chore(engine): trim the browser-lease comment to its live invariant
— Jerrai
* fix(core): a scene mounted as the index's own root takes its own master chain
When the index's top-level composition is itself the scene's mount, the standalone
extraction returned before the scene's chain was applied, so the index's chain still won.
— Jerrai
* fix(engine): windowed audio renders follow the file's clock and warm up for the whole chain
- Pass each window's start time into the page and on to buildFxChain, and seed the
pitch-shift worklet's tap phase from it, so chorus, phaser and pitch shift continue
across a window seam instead of restarting from time zero.
- Size the pre-roll from the chain (delay repeats, reverb room, dynamics release, to -60 dB),
never below the configured floor and never above a cap.
- Read a WAV data size of 0 or 0xFFFFFFFF as running to the end of the file, so audio past
4 GiB is no longer dropped.
- Close the input descriptor when the output cannot be opened, and name the real cause when
an input over 2 GiB cannot be streamed.
- Cover reverb, a slow-release compressor, chorus and pitch shift in the windowed-vs-whole
test, with an output-length check.
— Jerrai
* fix(engine): start each audio window on a render quantum and test the seams against a hard chain
Windows that begin off a 128-frame block see worklet blocks shifted against a whole render, which
left spikes in the tail after the last input sample. Align the window start, and make the
windowed-vs-whole test cover modulation, delay, reverb and a slow-release compressor on windows
whose start is not zero, failing above 0.001 where it previously tolerated 0.02.
— Jerrai
* fix(engine): keep a limiter's lookahead, seed bitcrush holds, close-check without /proc
- Pad the render source with the limiter's lookahead of silence so the last samples of a clip
leave the lookahead line instead of being dropped.
- Seed the bitcrush hold counter from the window's start so holds that do not divide the
render quantum stay in step across seams, and cover bitcrush in the windowed-vs-whole test.
- Check that the input descriptor is closed through the fs mock instead of /proc/self/fd,
which only exists on Linux.
— Jerrai
* test(engine): pick a bitcrush hold the window starts do not divide
The earlier hold of 3 divided both window starts, so an unseeded counter passed.
— Jerrai
* fix(engine): warn at the pre-roll cap, share tail formulas, read a zero data size alike
- Warn when a chain's memory exceeds the pre-roll cap, so cut repeats at window seams
are not silent.
- Export the delay and reverb tail formulas from audio-fx-tail and use them for the
window warm-up too.
- Both WAV readers now treat a declared data size of 0 or 0xFFFFFFFF as the rest of
the file.
— Jerrai
A 9:16 fixture with a looping A-roll video, an untimed full-frame backdrop above
it that the film's own GSAP script shows only from 6 s to 8 s, captions on top
and two sub-compositions. The check plays it in Studio's preview and reads the
preview's pixels and the element on top at the frame's centre: the video must
show at rest and while playing outside the backdrop's window, and the backdrop
must show inside it. It fails with #5458's visibility skip reverted.
* feat(audio): composition master bus (data-fx-chain on the root)
A clip can belong to only one audio group, so there was no way to put one
effect, such as a limiter, across the whole mix. The data-fx-chain on the
top-level composition root now runs once over the sum of every clip and every
group, after each group's own chain and fader. No new attribute: a sub-composition
root's data-fx-chain was never applied to anything and still is not.
Render: when a master chain exists, mixAudioTracks writes a float WAV instead
of AAC, applyAudioFxChain runs the chain over it, and ffmpeg encodes AAC last,
so the chain reads unclipped samples. An unreadable chain is a mix failure, as
for a group. Preview: the transport splices the chain between the clips' sum and
the monitor, re-anchored per play generation and watched for edits.
Only the top-level root is read. No master automation: the root's data-automation
is not read as the bus's. No lint rule: data-fx-chain is valid on clips, groups
and roots, and the one place it does nothing (a sub-composition root) has always
been silent.
— Jerrai
* fix(core): preview routes every audible video through the master bus
A root data-fx-chain is attached only inside the Web Audio graph, but a plainly
audible <video> joined that graph only for its own chain, group, automation or a
volume over 1. So preview limited the music and left the talking head dry, and a
composition whose only audio is video got no master chain at all, while export
sums every audible element into the master. A master chain on the top-level
composition root is now a reason to join, the same as a video's own chain.
Elements under a root with no chain keep native output. Sub-composition roots
are still ignored.
— Jerrai
* fix(audio): master bus review round 2 (temp dir, tests, docs, Studio meter)
- audioMixer: a fatal FX error in the master pass or a group bus left the
work directory (the full float sum, about 1.4 GB an hour) on disk. The
final mix now runs in try/finally and the group rethrow removes it first.
- Tests: the "once over the sum" case now uses a limiter then a gain, so a
per-clip chain (-10.4) and a doubled one (-18) both fail it; a new case
proves the sum stays float until the chain reads it; the fader case uses
a group volume of 0.5 so the fader's position is exercised; the work-dir
removal is covered for both paths.
- Docs: "Three rules exist" (the master-bus lint rule is gone), a bad master
chain stops the render with audio_processing_failed, a sub-composition
file opened on its own has its root chain as the master bus, and a seek
clears a group or master limiter.
- Studio: TimelineElement.masterChain carries the root's data-fx-chain, so
the program meter shows for a video-only composition whose only audio
routing is the master chain, as preview now plays it.
- Comment blocks over four lines cut; webAudioTransport.setRate split so
its CRAP score is under the Fallow threshold.
— Jerrai
* fix(audio): master chain reaches tracks the render adds; honest plain-limiter copy
- Master chain: it was carried on each parsed AudioElement, so a track the
producer adds itself (appendAutoDetectedVideoAudio, the browser-discovered
audio in probeStage) never had it, and a composition whose only audio was
such a track exported with no master chain, no ceiling and no warning. It is
now a composition property: parseMasterFxChain(html) at compile, carried on
CompiledComposition and CompositionMetadata, passed to processCompositionAudio
by runAudioStage (in-process and distributed plan). AudioElement.masterFxChain
is removed so there is one source. Each forward (compile stage, local
orchestrator, distributed plan) has a test that fails when it is dropped.
- Plain limiter: it follows the level with no lookahead and Level after is
applied past the ceiling, so it cannot promise "nothing gets past" or "never
clips". Studio copy (description, panel text, knob label, summary) and the
hyperframes-audio skill now say a fast or inter-sample peak can pass it and
point to the true-peak limiter for a delivery limit. A copy test guards it.
— Jerrai
* fix(producer): keep master bus comments within the ratchet; run the plan test with the ffmpeg tests
Drop three one-line field comments that raised their files' comment share,
and list plan.masterBus.test.ts with the integration tests because plan()
reads the ffmpeg version.
— Jerrai
* chore(ci): repin the producer test-classification source in the reachability map
The reachability map pins the hash of test-classification.mjs, which now
lists one more integration test. The map's producer lane command already
covers every test under src, in both the unit and integration matrix lanes.
— Jerrai
* feat(cli): add hyperframes/api entry with transcribe and removeBackground
HyperFrames Desktop runs the CLI as a child process, relaunching Electron
with ELECTRON_RUN_AS_NODE=1. A programmatic entry lets it import plain
functions and run them in a utilityProcess instead, so the RunAsNode fuse
can be turned off.
packages/cli/src/api.ts exports transcribe() and removeBackground(): typed
options in, typed result out, progress through callbacks, failures thrown
as TranscribeError / RemoveBackgroundError with a code and the original
error as cause. Neither prints, reads argv or exits.
The transcribe and remove-background commands now keep only flag
handling, printing and exit codes, and call these functions. Their
output, JSON and exit codes are unchanged.
The package gains an exports map with "./api" (built dist plus d.ts) and
"./package.json", generated from package-subpaths.json like the sibling
packages.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): trim api.ts comments under the package's comment share
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): trim trailing dots without a quadratic regex
CodeQL flags the trailing-dots regex on the Parakeet error now that it sits
behind a library entry. Same result, linear time.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): make the transcribe API's signal stop every stage
Review follow-ups on the api entry:
- parakeet-mlx now runs as an async child that the caller's signal can
kill. Its failures keep the synchronous run's message, status and stderr.
- A pre-aborted signal throws before any setup. Whisper checks the signal
before each setup stage, and the model download gets it.
- onEngine reports the model whisper runs (small for Spanish, not small.en).
The resolver moved to whisper/modelForLanguage.ts so both share it.
- api/transcribe.ts imports everything at module scope. The command test
hoists its whisper mock, since that module now loads during imports.
- whisper's internal TranscribeOptions/TranscribeResult types are renamed
WhisperOptions/RunnerResult, so the API's public names are unique.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(cli): let an AbortSignal stop whisper's install and audio prep
Review follow-ups on the api entry:
- whisper's runtime install (brew, git clone, cmake) and its ffmpeg audio
prep run through a new stoppableExecFile when an API caller passes a
signal. The child runs off the event loop, an abort ends it and every
process it started, and the call rejects with a "cancelled"
TranscribeError once they are gone. A failure throws the same error
execFileSync throws (message, code, errno, status, signal, stdout,
stderr), compared field by field in stoppableExecFile.test.ts.
parakeet-mlx uses the same helper.
- Without a signal the helper is execFileSync, and the CLI passes none, so
the CLI output stays byte for byte the same as hyperframes 0.8.146.
- TranscribeOptions.signal now says what it covers: a tool lookup that is
already running (capped at 10 seconds) finishes first.
- New regressions in api.test.ts abort during a stand-in install step and
during a stand-in ffmpeg convert. Both end in under 10 seconds with no
process left, and both fail with the old synchronous calls.
- Tests load every module at the top of the file: namespace spies replace
the importOriginal partial mocks, and node:fs and node:child_process are
spied through their default exports and synced with
syncBuiltinESMExports. remove-background --info imports its manager
statically. The scanner in api.test.ts now reads every file this PR
touches and catches importOriginal and vi.importActual.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(core): an element its script hides stays hidden while studio preview plays
* fix(core): a stamped element re-timed in the timeline hides outside its new window
Studio timing edits drop the runtime-stamped mark, and a script-hidden stamped
container no longer holds back its images.
* fix(core): a preview-only stamp copied into a project no longer changes the render
Only the Studio preview honours the stamp, copied live markup drops it, and a
lane-only move makes a stamped element authored like the save does.
* fix(engine): exports place sound and picture right when a source's audio and video start apart
* fix(engine): a late video stream plays to its end and distributed and HDR renders hide its lead
* test(engine): pin a late video stream's trimmed start, held tail and HDR trim
* fix(studio): keep the shortcuts panel above the player controls and inside the window
The panel was drawn inline next to its button at z-50, so a host layer
above that (the desktop's playback bar, z-55) covered its Playback rows,
and the preview pane's overflow clip cut its top off in a short window.
It now opens through Studio's Popover on the shared popup layer: portaled
out of the button's boxes, held above the button, and capped to the room
there with a scrolling body.
* fix(studio): the shortcuts button keeps its tooltip description for screen readers
* fix(engine): a capture stall names the step Chrome stopped answering in
* fix(engine): a stall on a motion-blurred frame names the seek or injection it hung in
* fix(cli): media-treatment applies one treatment to every matched image or video
* fix(cli): media-treatment reports its action from every target and documents the per-element merge
* fix(cli): media-treatment patches each match by its stable id so one write cannot redirect another
* fix(cli): media-treatment sets every match in one parse instead of re-finding elements
* fix(cli): media-treatment refuses media that is not an HTML element, as before
* chore(skills): refresh the media-use manifest hash for the treatment reference edit