381 Commits
Author SHA1 Message Date
Miguel Ángel aca4bc2f49 chore: release v0.8.113 (#4913) 2026-10-02 12:14:00 -07:00
miga-heygenandMiguel Ángel 821e8c8ad0 fix(cli): hide console windows on Windows for background/detached spawns (#3996)
* fix(cli): hide console windows on Windows for background/detached spawns

Add windowsHide: true to 8 spawn/execFile call sites that were missing
it: ffprobe/ffmpeg probes in the producer and engine packages, the
telemetry exit-flush and studio dev-server spawns, and the browser-open
and background-preview detached launches in the CLI.

previewLifecycle.ts's spawnDetachedPreview redirects stdout/stderr to a
raw log-file descriptor; libuv only adds Windows' CREATE_NO_WINDOW flag
when no stdio slot inherits a raw handle, so windowsHide alone does not
fully suppress a console flash there (documented inline) — it's still
added for consistency and because it correctly sets the process's
show-window state regardless.

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>

* fix(cli): correct windowsHide flash-risk claims and add missing dev-server spawn tests

- The previewLifecycle.ts comment (and its mirrored test comment)
  overstated the residual console-flash risk on the detached background
  preview spawn: DETACHED_PROCESS already leaves that child without a
  console regardless of whether CREATE_NO_WINDOW gets applied.
- Add the two missing regression tests for preview.ts's runDevMode and
  runLocalStudioMode spawns, exporting both for test access.
- Drop an unused export in audioPadTrim.ts flagged by the repo's dead-code
  gate; the constant is only ever used within its own file.

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>

* fix(engine): hide console window for the drawElement self-verify PSNR comparison

psnr.ts's execFile call runs in-process inside the Studio server's render
path (parallelCoordinator.ts's drawElement disk self-verify), so a render
started from Studio's UI under a detached preview hits this call from a
console-less parent — the same flash case the other windowsHide sites in
this PR already cover. psnrFilterAvailability.ts's probe is on the same
in-process render path and already had the flag.

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>

* fix(cli): keep only the spawn sites main still misses and leave browser windows visible

windowsHide maps to libuv UV_PROCESS_WINDOWS_HIDE, which also starts GUI apps with SW_HIDE,
so the detached browser launch must not set it. The background preview child already has
no console (DETACHED_PROCESS with inherited log fds), so its flag was a no-op. The
telemetry, psnr and audioPadTrim sites shipped on main via #3839 and #4604.

* fix(cli): stop the studio dev server when the terminal closes

With windowsHide the dev server runs in its own hidden console on Windows, so closing the terminal
only signals the wrapper (SIGHUP). Handle it like SIGINT and SIGTERM and kill the process tree.

* fix(studio-server): hide the console window for loudness measurement

The background preview has no console, so its ffmpeg loudness run opened a visible window on Windows.

* refactor(cli): drop the comment on the studio shutdown signals

---------

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-10-02 17:15:14 +00:00
Miguel Ángel ea55218eba chore: release v0.8.112 (#4909) 2026-10-02 08:36:30 -07:00
Miguel Angel Simon Sierra 8447a55a1d chore: release v0.8.111 2026-10-02 01:32:47 -07:00
Vance Ingalls ec6363d3b6 chore: release v0.8.110 2026-10-02 00:31:59 -07:00
Miguel Angel Simon Sierra 6691e59b1a chore: release v0.8.109 2026-10-02 00:02:36 -07:00
Vance IngallsandClaude Opus 5.5 45b8e171e2 feat(studio): sync origin, out-of-sync repair, linked selection toggle and grouped clip menu (7/8) (#4820)
* feat(studio): order the clip menu into time, sound, picture and delete groups

Split and Freeze frame; Normalize, Voice, link items and Duck; Look and Crop;
then Delete beside Delete this clip only. Each item file renders its pieces
separately, empty groups collapse, and the viewport clamp uses the measured
menu height instead of a row-count estimate.

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

* fix(studio-server): mint freeze-frame right-half links with the shared lk-N helper

Freeze frame now calls core relinkSplitHalves, so split halves and freeze
halves share one link-id convention instead of L -> L-2.

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

* feat(studio): apply speed edits to every data-link member in one undo step

The inspector speed slider, rate lane edits and ramp presets on a linked clip
now commit once, writing the selection and, via expandToLinkedMembers, each
partner from its own current value. Unlinked clips keep their usual path.

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

* refactor(studio): drop the audio parent tick in favour of the data-link chain badge

A linked clip now shows one link indicator: the chain badge driven by
data-link. The waveform no longer draws its 1px parent tick.

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

* fix(studio): follow late clip-menu rows and target link partners like S2

The menu height follows a ResizeObserver so rows that read the live preview
after mount still clamp inside the viewport. Speed fan-out builds partner
targets with buildPatchTarget, matching the link edit plan.

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

* fix(studio): show the sound strip and audio menu items without studio's shell

The audible-video waveform strip drops AudioWaveform's 16px label band, which
left a 0px canvas on short lanes. Normalize loudness and Duck under voice fall
back to the timeline session's project, the live preview iframe store and a new
onNotice edit callback when no StudioShellProvider is mounted.
usePreviewIframeStore is exported so a host can publish its preview.

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

* fix(studio): show duck under voice only when a voice overlaps

The item now appears only when an overlapping voice exists or the bed is
already ducked. Its check mark moves to the right edge so unchecked rows
align with siblings.

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

* feat(studio): name the partner in the clip menu link and unlink items

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

* fix(studio): clip menu stops offering unlink once the clips are unlinked

The timeline skipped store updates when only data-link changed,
so the menu kept reading the stale link.

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

* feat(core): sync origin, offset math and move/slip into sync for linked pairs

data-sync-origin survives Unlink; split gives right halves a fresh origin.

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

* fix(studio-server): split re-mints the sync origin of unlinked pairs too

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

* feat(studio): detach, link and keep-sound cutout write a sync origin that unlink keeps

TimelineElement.syncOrigin is read on both parse paths; merge back drops it.

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

* test(studio): expect sync-origin on keep-sound cutout write

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

* feat(studio): linked selection toggle in the timeline toolbar, on by default

Off: a click selects one clip; drag, trim, razor, speed and delete stay on it
(delete unlinks the survivor). Alt-click still selects one half when on.
Right-click holds the out-of-sync indicator preference. Both persist in UI prefs.

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

* feat(studio): link groups share a label colour; out-of-sync badges with move and slip

Red badge on both halves of a sync-origin pair that drifted (frames, s:ff past
a second); hidden when rates differ or the preference is off. The whole badge
opens Move / Slip into Sync; each edits that clip alone in one undo step.

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

* fix(studio): take link label colours from theme tokens

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

* feat(sdk): syncOffset, moveIntoSync and slipIntoSync for sync-origin pairs

Core gains findSyncPartner, the DOM pairing shared by the SDK and the CLI.

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

* feat(cli): timeline rows report syncOffsetFrames and print out-of-sync

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

* test(cli): the timeline row fixture carries syncOffsetFrames

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

* docs(skills): data-sync-origin in hyperframes-core

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

* fix(core,sdk): keep a one-sided cut's sync origin and slip the in-point playback reads

relinkSplitHalves reminted the right half's sync origin even when only one
member of an unlinked source pair was cut, so the uncut partner kept the old
origin and the new half lost its badge. The origin is now reminted only when
both the video and the audio were cut.

Slip into Sync wrote data-media-start whenever it existed, but playback
reads data-playback-start first, so a clip carrying both never moved. The
link timing reader now uses playback's precedence and the slip writes the
attribute that wins; lint compares the same field. A sync partner is also
looked up only in the clip's own composition (core) or source file (Studio).

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

* fix(studio): flip the out-of-sync menu above the pointer near the bottom edge

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

* refactor: trim comments the sync-origin layer added

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

* refactor(core): split the scoped candidate lookup out of findSyncPartner

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

* fix(core,studio): find sync partners inside the link scope

findSyncPartner and Studio's syncPartnerOf now scope by the same rule as
link groups: the nearest data-composition-id or data-composition-file
ancestor, plus the row's compositionScope in Studio. An origin reused in a
file host or an inline composition no longer pairs across it.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 06:08:37 +00:00
Vance IngallsandClaude Sonnet 5.5 0559be0b13 feat(studio): link a video and its audio with data-link (6/8) (#4818)
* feat(core): add data-link media link helpers

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

* feat(lint): warn on out-of-sync and orphaned linked clips

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

* feat(studio): select, drag and trim linked clips together

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

* feat(studio): detach, merge, link and unlink source transforms

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

* feat(studio): link-aware split, delete, alt-edit unlink and link edit actions

Split and delete fan out to link partners. Alt drag/trim edits one member
and unlinks it. onLinkEdit runs unlink, link, detach and merge as one undo
step each. Single-clip timing edits of a linked clip skip the SDK path so
its linked default cannot move the partner behind the store.

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

* feat(studio-server): give split right halves of a linked pair their own link

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

* feat(studio): clip menu detach, unlink, link, merge back and delete-one items

Items live in clipMenuLinkItems.tsx with a one-line hook in ClipContextMenu;
the same resolver drives the Cmd+L, Opt+Shift+D and Opt+Delete shortcuts.

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

* feat(sdk): setTiming applies to data-link partners by default

{ linked: false } edits one member and unlinks it, matching Studio's Alt-edit.

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

* docs(skills): document data-link linked clips in hyperframes-core

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

* chore(studio): keep comment share at or below base in link plumbing

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

* test(studio): audio pill link follows data-link, not a shared file name

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

* fix(studio): alt-click selects one linked member even inside the pair selection

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

* refactor(lint): drop the rule-list comment for the linked clip rules

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

* fix(sdk,lint): keep a link group inside its own composition

Link ids are minted per file, so lk-1 is normal in the root and in a child
composition. The SDK's linked-timing query descended into nested hosts and
addressed every match with the initiating scope, so moving a root clip moved
the child's pair too; lint grouped them into one drifting group. Both now
group only members of the same composition scope.

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

* fix(studio): match linked media by full asset path, scope links, keep hidden sound silent

Merge and Link compared only a lowercased basename, so assets/one/talk.mp4
and assets/two/talk.mp4 enabled a destructive Merge. Clips now compare the
src resolved against each clip's own source file, and Merge re-checks the
two srcs in the source before it runs. Link membership in the timeline
(linked members, gesture expansion, unlink orphans) is scoped to a source
file. Detach and the keep-sound cutout carry a hidden video's data-hidden
to its audio, and Merge refuses when only one member is hidden.

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

* refactor(studio): move the timeline move-edits handler into its adapter

App.tsx sat at 600 lines on this layer and 602 once merged with main.
useTimelineMoveEditsHandler now lives beside persistTimelineMoveEditsAtomically,
same callback and dependencies, bringing App.tsx to 592.

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

* fix(sdk,studio,lint): keep link groups inside inline compositions

A link group now stays inside the nearest data-composition-id or
data-composition-file ancestor. The SDK used to recognise only file hosts,
and Studio compared only sourceFile, so an inline child composition that
reused a link id was edited together with the root group. core/media-link
linkScopeOf is the shared rule for the SDK and Studio. Lint cannot import
core, so it uses the same two attributes directly. Studio rows record their
compositionScope, and link and merge refuse pairs that cross it.

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

* fix(sdk): refuse a link edit whose partner address names another clip

A partner addressed in the caller's scope can resolve to a different element
when an inline composition reuses a root clip's data-hf-id. setTiming now
checks that each partner's address resolves back to that partner and throws
before mutating when it does not, instead of editing the root clip.

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

* fix(studio): a remote media url keeps its query when matching an asset

Two different videos at one remote path with different queries were treated as
the same file, so Merge could delete the audio and use the wrong video's
sound. Project-local srcs still drop query and hash.

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

* fix(studio): keep a remote url's query exactly as written when matching assets

Decoding the whole url folded %26 into & so two different media urls matched.
Decode only the path; the query is compared as authored.

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

* fix(studio): compare a remote url as serialized, decode only local paths

A remote pathname with an encoded reserved character (%3F) matched the same
path with a literal ?, though they can return different media. Remote urls
keep origin, path and query exactly; local srcs are still decoded and
stripped of query and hash.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 05:23:25 +00:00
Miguel Ángel e205207583 fix(studio): a text edited in place shows its caret in a scaled preview (#4875)
* fix(studio): a text edited in place shows its caret in a scaled preview

* fix(studio): the drawn caret follows a resized preview and stands right at bold words and empty lines

* fix(studio-server): a write receipt's replaced version comes only from the bytes it overwrote

* fix(studio): the drawn caret stands on the selection inside a transformed canvas

* test(studio): unmount the caret tests' roots before clearing the page

* test(studio): the caret gate waits for each key to land and retries a text that does not open

* test(studio): the caret gate checks what the DOM can tell at a soft wrap, and shows a text that would not open

* test(studio): the caret gate opens a text with a double press when click and Enter do not

* fix(studio): the drawn caret follows a pan or zoom of the preview

* fix(studio): the inline text style bar stands by the selected text inside a transformed canvas

* fix(studio): after End at a soft wrap the drawn caret stays at the line's end, as the system caret does

* fix(studio): only End keeps the drawn caret at a wrapped line's end, as Chrome's own caret

* refactor(studio): share the test mount helper and split the caret's placement for the code gates

* docs(studio): trim the drawn caret's comments to the package's share

* fix(studio): open inline edit on styled text whose nodes come from another window

* refactor(studio): name the realm-safe html check instead of commenting it

* docs(studio): shorten the caret fixture comment to the 120-character limit
2026-10-01 22:31:23 -07:00
Miguel Angel Simon Sierra a63c8d0577 chore: release v0.8.108 2026-10-01 22:20:30 -07:00
Vance IngallsandClaude Opus 5.5 824e4100c6 feat(studio): waveform strip, clip light, peak marks and video beat source (5/8) (#4817)
* feat(studio): draw a waveform strip under video clips that carry sound

An audible video keeps its thumbnails and gains the audio waveform along the bottom
38% of the clip (also when thumbnails are off); muted and silent video are unchanged.
The waveform route already decodes video audio; a test now proves it on an mp4.

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

* feat(studio): latch a CLIP light on the master meter at the export ceiling

The light turns red when the preview master sample peak reaches -1 dBFS (the export
true-peak ceiling) and stays lit until clicked. Mutation-tested threshold and latch.

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

* fix(studio): paint the CLIP light from theme colour tokens

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

* feat(studio): mark where each clip redlines on its waveform

studio-server computes an absolute per-50ms sample-peak map once per media file
(native channels, cached by size+mtime next to the waveform cache); the timeline
paints red marks where the source at the clip's own volume reaches -1 dBFS and
names the loudest point.

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

* feat(studio): let a video with sound be the beat source

Studio's isMusicSourceElement accepts an audible, unmuted video for the music track
and the beat fallback (lane zoning unchanged); core findMusicAudioSrc accepts
<video data-has-audio="true"> without muted; the beats CLI message mentions video.

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

* fix(studio): decode each peak map once and never offer Duck on a carve source

Concurrent peak requests for one file share a single ffmpeg decode; Duck under voice
is withheld on a clip another bed already carves against.

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

* fix(studio-server): decode the same audio stream the peak map probes

The channel probe read stream a:0 while the decode left stream choice to
ffmpeg, which picks a later stereo stream over a mono first one; every frame
was then read at the wrong width, doubling the bins and taking the other
stream's level. Both commands now select a:0, and the cache key moves to
peaks-v2 so maps built from the wrong stream are rebuilt.

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

* fix(studio-server): terminate the peak map's ffprobe options before the input

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

* fix(studio-server): reject a peak map whose decode exits nonzero

A decode that emits some samples and then fails no longer resolves as a
complete map, so /peaks cannot cache a truncated clipping map.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 04:43:26 +00:00
Vance IngallsandClaude Opus 5.5 9244c47328 feat(studio): normalize loudness and duck under voice, with a limiter report (4/8) (#4816)
* feat(studio-server): share loudness measurement with an absolute target and video support

normalize-audio moves its measure/plan code into @hyperframes/studio-server/loudness,
gains an absolute mode (--lufs, default -16, clamped at +12 dB and -1.5 dBTP) and
accepts <video> clips with sound. Adds the @hyperframes/core/audible-video subpath.

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

* feat(studio-server): add a measure-only loudness normalize endpoint

POST /projects/:id/loudness/normalize measures a clip's played window (audio or
video) and answers the -16 LUFS data-volume plan; Studio writes the attribute.

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

* chore: register the loudness and audible-video subpaths in their descriptors

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

* style: format the loudness and audible-video subpath descriptors

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

* feat(studio): add Normalize loudness and Duck under voice to the clip menu

Normalize measures the clip on studio-server and writes data-volume as one undoable
edit; Duck toggles a carve against every overlapping voice (audio or video with
sound) at the default strength. Both live in clipMenuAudioItems with one hook-in.

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

* refactor(studio): tighten carveOutput docs to the package comment share

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

* feat(producer): report how far the true-peak limiter lowered the export

The AAC true-peak pass now returns its attenuation; the render logs "Audio lowered
by X.X dB to stay under -1 dBTP" and sets RenderJob.audioLoweredDb, which Studio
servers carry in the complete SSE event and the /renders record. Also exports
useAudioMetersVisible from @hyperframes/studio for hosts.

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

* fix(studio-server): scan HTML start tags without a backtracking regex

The start-tag regex backtracked quadratically on '<' followed by many spaces
(CodeQL js/polynomial-redos). A linear character scan reads the same name.

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

* fix(studio): refuse to normalize a volume-automated clip and honour save outcomes

A volume lane owns the clip's gain in preview and render, so writing
data-volume changed nothing while the toast claimed success; Normalize now
refuses with a clear message. Normalize and Duck also check each quiet-save
outcome, stop at the first failed or refused write and report it.

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

* chore(core): drop the duplicate audible-video subpath descriptor

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 03:51:34 +00:00
Vance IngallsandClaude Opus 5.5 000ccd426a feat(studio): speed presets, voice, look, crop presets and freeze frame in the clip menu (3/8) (#4815)
* feat(core): add gentle ramp-in, ramp-out and slow-mo-middle speed presets

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

* feat(studio): add voice, look and crop clip-menu items and clip badges

Voice and Look are single-choice submenus writing data-fx-chain and
data-color-grading; None clears. Crop opens the canvas crop preset bar.
ClipBadges reads the live preview node and follows attribute edits.

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

* fix(studio): drop the unstyled badge classes

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

* feat(studio): add crop aspect presets to the canvas crop handles

Free, 16:9, 9:16, 1:1, 4:5, Reset and Done float above the clip opened from
the clip menu. Presets commit a centred inset clip-path; Reset removes it.

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

* refactor(studio): read the committed crop from the inline clip-path

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

* feat(studio-server): add freeze-frame file mutation

Extracts the frame under the playhead with ffmpeg (media time honours
data-media-start, playback rate and a rate lane) into assets/freeze/, then in
one write splits the video, inserts a 2 s <img> still with the video's box
styles, shifts the rest of the track and splits a data-link partner.

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

* fix(studio-server): import replaceFileAtomically from core atomic-file

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

* feat(studio): add freeze frame to the clip menu

Freeze frame on a video under the playhead posts the studio-server
freeze-frame mutation and records its before/after as one undo step.
onFreezeFrame is threaded through the timeline edit callbacks.

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

* fix(studio-server): keep the freeze still inside assets/freeze

The clip id from the project HTML went straight into ffmpeg's -y output
path, so an id with ../ segments wrote outside the project. The id is now
reduced to one safe filename component, the output is pinned inside
assets/freeze before extraction, and the still's src is built from that
name rather than from a filesystem path.

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

* fix(studio-server): give every freeze still its own immutable name

Two clip ids that sanitise alike (a.b / a_b), ids sharing an 80-character
prefix, or a second freeze of one clip at the same playhead all produced the
same still path, and ffmpeg's -y replaced the first still's pixels. The name
now carries a hash of the raw id and a per-extraction token, the route
refuses a name that already exists, and ffmpeg runs with -n so it can never
overwrite. The freeze media time reads data-playback-start before
data-media-start, as playback does.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 03:08:50 +00:00
Miguel Angel Simon Sierra c3a20c589d chore: release v0.8.107 2026-10-01 19:05:00 -07:00
Miguel Ángel d4756f597c fix(studio): edits on GSAP-animated layers keep their keyframes and file positions (#4869)
* fix(studio): resizing an element GSAP only positions saves its CSS width and height

* fix(studio): a resize never saves GSAP's scale mask as the element's CSS

* fix(studio): keep the resize size writer inside the file and comment budgets

* fix(studio): a GSAP-positioned resize saves its CSS size before the GSAP write renders

* fix(studio): undoing a crop leaves a GSAP-animated layer where its file puts it

* test(studio): an undo on a GSAP-parsed layer in a two-script file reloads the preview

* fix(studio): a move keeps an authored xPercent centring set in the saved file

* fix(studio): a drag on a centred element no longer flashes off-centre on release

* fix(studio): a font pick saves the edit and its @font-face in one write

* fix(studio): a focused combobox, switch or plain-text editor counts as an unsaved edit

* test(scripts): pin that a slash after i-- before a quoted slash is division

* fix(studio): an inspector edit saved while a preview reload loads stays on screen

* fix(studio-server): a font save accepts exactly one @font-face rule and nothing after it

* fix(studio-server): a font name or path with braces in quotes still saves its @font-face

* fix(studio-server): a CR or form feed in a quoted font name cannot end the @font-face rule

* test(studio): bank the 99 edit accuracy cases the GSAP fixes now pass
2026-10-01 17:24:35 -07:00
Miguel Ángel 30c55694cb fix(studio): a first animation in a sub-composition file runs from inside its template (#4806) 2026-10-01 13:40:55 -07:00
Miguel Ángel c72b735a8c fix: a save's temp file no longer reaches Studio as an outside change (#4857)
* fix: a save's temp file no longer reaches Studio as an outside change

* refactor: the project file walk and its tests carry no new duplication or complexity

* test(studio): the edit accuracy gate judges the two nudge sequences again
2026-10-01 11:48:00 -07:00
Miguel Ángel 89704189d8 chore: release v0.8.106 (#4873) 2026-10-01 11:46:50 -07:00
Miguel Ángel 59bbe7f098 feat(studio-server): a write receipt names the version the write replaced (#4864)
* feat(studio-server): a write receipt names the version the write replaced

* test(studio-server): route receipts carry the version each write replaced

* refactor(studio-server): the receipt's from field needs no comment
2026-10-01 11:13:20 -07:00
Miguel Ángel a9929b60ee fix(studio): the preview serves a same-size edit written right after the last one (#4792)
* fix(studio): the preview serves a same-size edit written right after the last one

* chore(studio): keep the comment share and complexity gates green

* refactor(studio): keep the project signature doc comment on the function it describes

* fix(studio): the preview re-reads files written in the last two seconds, whoever wrote them
2026-10-01 11:10:58 -07:00
Miguel Ángel b6c879c4e9 fix(studio-server): a folder rename past an unreadable folder or file answers ok (#4860)
* fix(studio-server): a folder rename past an unreadable folder or file answers ok

* fix(studio-server): a rename skips only folders and files it may not read

* fix(studio-server): the rename's skip helpers say it by name

* test(studio-server): the rename tests share one request
2026-10-01 08:41:39 -07:00
Miguel Ángel bc57e282fd chore: release v0.8.105 (#4858) 2026-10-01 07:33:11 -07:00
Miguel Ángel 0db5b0a285 chore: release v0.8.104 (#4850) 2026-10-01 03:53:24 -07:00
Miguel Ángel 86a7e8cc75 chore: release v0.8.103 (#4838) 2026-10-01 00:15:00 -07:00
Miguel Ángel a369708fa5 fix(studio): open projects named with a colon and keep preview assets for names with # (#4829)
* fix(studio): open projects whose folder name has a colon

* fix(studio): open projects named with a colon or '#' in Studio and its preview

* fix(studio): refuse a Windows stream id reached through a session alias

One adapter rule, isServableProjectId, now guards the requested id, a
session's projectId and the folder listing. The bundler-failure preview
places its <base> inside an attributed <head>, and the media metadata
cache keys on the encoded URL so ids with ':' can't collide.
2026-09-30 23:21:53 -07:00
Miguel Ángel ed20d67b16 chore: release v0.8.102 (#4835) 2026-09-30 23:04:17 -07:00
Miguel Ángel be80a30802 chore: release v0.8.101 (#4831) 2026-09-30 21:12:22 -07:00
Miguel Ángel e29433c77e chore: release v0.8.100 (#4828) 2026-09-30 20:24:37 -07:00
Miguel Ángel 132b9909fa chore: release v0.8.99 (#4800) 2026-09-30 22:41:49 +00:00
Miguel Ángel 299670e210 fix: stop two merge-queue flakes in the history prune and timeline tests (#4786)
* test(studio-server): history prune tests allow a file time a moment past Date.now()

* ci: temporary probe for the history prune test flake

* fix(studio): timeline scroll-end timer no longer fires after unmount

* ci: remove the temporary history prune probe

* test(studio): check the virtualizer teardown with a spy and a 200 ms wait
2026-09-30 20:51:39 +00:00
Miguel Ángel f1c1f1d69c fix(parsers): moving a clip on the timeline moves its inner animations too (#4761)
* fix(parsers): moving a clip on the timeline moves its inner animations too

* fix(parsers): a timeline move keeps chained and partly known tweens where they belong

* fix(parsers): moving a clip leaves chained outside tweens and template aliases in place

* fix(studio): a clip timing edit syncs its animations once, not twice

* fix(studio): a timing edit falls back to the server when the SDK wrote no animation change

* fix(studio): the SDK sync check reads scripts inside a template too

* fix(studio): the SDK sync check finds template scripts through the DOM walker

* fix(core): a clip retime writes only its own timed tweens, never outside ones

* style(core): format the clip tween import
2026-09-30 20:03:48 +00:00
Miguel Ángel 567525a8c2 fix: readers never see an empty or half-written project file (#4777)
* fix: readers never see an empty or half-written project file

* test: pin the create routes and block install to whole-file writes

* fix(sdk): a write through a link to a missing file creates that file

* fix: saves through a linked folder reach the real file, atomic writes handle long names

* test(sdk): check for the stray file, not the folder listing

* fix(core): creates work on Windows FAT drives, and links through folders resolve as the system does

* fix: agree the helper move in the deletion check, and match temp paths without a path regex

* test: check POSIX file modes only off Windows
2026-09-30 19:17:59 +00:00
Miguel Ángel 04b01342cf chore: release v0.8.98 (#4788) 2026-09-30 18:43:57 +00:00
Miguel Ángel 9ae1e3c842 chore: release v0.8.97 (#4775) 2026-09-30 14:08:38 +00:00
Miguel Ángel 5461e0ab03 chore: release v0.8.96 (#4752) 2026-09-30 05:17:09 +00:00
Miguel Ángel f5a348e92d chore: release v0.8.95 (#4751) 2026-09-30 03:47:02 +00:00
Miguel Ángel daa44fcd75 chore: release v0.8.94 (#4744) 2026-09-29 17:41:30 -07:00
Miguel Ángel 2eeaefb3e5 chore: release v0.8.93 (#4733) 2026-09-29 13:14:35 -07:00
Miguel Ángel 6d00d94b46 chore: release v0.8.92 (#4729) 2026-09-29 15:47:29 +00:00
Miguel Ángel 481dae1ed9 fix: render names a folder composition source instead of crashing (#4613)
* fix(producer): render names a data-composition-src that points at a folder instead of crashing

* fix(lint): name a data-composition-src that points at a folder instead of crashing

* fix(cli): compositions, keyframes and Studio insertion survive a folder composition source

* fix(lint): tell a folder composition source to point at the HTML file inside it

* fix: read a composition source through one file handle so its type check and read cannot race

* fix(parsers): report a named pipe as a composition source instead of blocking on it

* revert(parsers): open composition sources read-only again, which CodeQL reads as no file creation

* fix(parsers): open composition sources with a private mode, skip pipes, and treat bad paths as missing
2026-09-29 07:58:17 +00:00
Miguel Ángel dcff54749c chore: release v0.8.91 (#4726) 2026-09-29 06:33:17 +00:00
Miguel Ángel e4fbc84874 chore: release v0.8.90 (#4721) 2026-09-28 19:29:59 -07:00
Miguel Ángel 9e1dbca7a2 fix(studio): undo and redo update the preview in place instead of reloading it (#4718)
* fix(studio): undo and redo swap the restored scene in place instead of reloading

* fix(studio-server): a preview asked for right after an undo shows the undone state

* fix(studio): an undo that restores a page outside every scene still reloads in full

* fix(studio-server): a preview built across a write is not cached under the old signature

* test(studio): a soft undo keeps the scene hashes so the next edit still swaps
2026-09-29 02:03:56 +00:00
Miguel Ángel ea4893602a chore: release v0.8.89 (#4720) 2026-09-28 19:10:32 -07:00
Miguel Ángel 343cf381f7 chore: release v0.8.88 (#4719) 2026-09-28 18:33:19 -07:00
Miguel Ángel 82ae73a031 chore: release v0.8.87 (#4717) 2026-09-28 18:02:10 -07:00
Miguel Ángel 663b02297d feat(cli): add hyperframes clean to find and remove what HyperFrames left on disk (#4701)
* feat(cli): add hyperframes clean to find and remove what HyperFrames left on disk

`hyperframes clean` lists leftover render work dirs, debug renders, the history of projects
that no longer exist, idle video proxies and extracted video frames, with sizes, and removes
them. `--dry-run` removes nothing, `--json` is for agents, `--snapshots` also removes
snapshot folders (otherwise listed as reclaimable). It never touches outputs, sources or
anything a running render or preview still owns.

* docs(cli): say that clean does not search a project's subfolders

* fix(cli): clean test compares folder names on Windows and the skills manifest is regenerated

* fix(studio-server): the proxy cache sweep removes only files the transcoder named

It removed any idle .mp4 or .webm in .transcode-cache, so a user file placed there, or reached through
a symlinked cache folder, could go. Proxies are always <sha256>.<ext> and their temp files
.tmp-<uuid>-<proxy name>; nothing else is swept.

* style(producer): keep a renderDirOwner doc comment within 120 columns
2026-09-29 00:43:42 +00:00
Miguel Ángel 498643bea4 fix(studio-server): the server loads where sharp's native binary is missing (#4654)
* fix(studio-server): the server loads where sharp's native binary is missing

* fix(studio-server): a missing sharp warns once and is checked before the image is read
2026-09-28 23:48:24 +00:00
Miguel Ángel 381fb0fd9a fix(studio-server): undo keeps an outside change even when Studio's history claim arrives late (#4631)
* test(studio-server): a write landing between Studio's read and its patch survives undo

* test(studio-server): the outside write also survives a claim that arrives after receipts expire

* fix(studio-server): undo keeps an outside change even when Studio's history claim arrives late

* fix(studio-server): history forgets replaced bytes on delete and frees unused bytes past budget

Also recreates the history folder before storing replaced bytes, hears writes inside the queued start, and pins the agent-turn claim that arrives after the receipt expired.
2026-09-28 23:48:13 +00:00
Miguel Ángel 1004b69829 chore: release v0.8.86 (#4695) 2026-09-28 13:37:47 -07:00