Commit Graph
5114 Commits
Author SHA1 Message Date
Miguel Angel Simon Sierra 1f8f97047c test(studio): the undo socket test holds round 0 to a full restore too 2026-10-02 03:39:04 -07:00
Miguel Angel Simon Sierra 3cc60107e1 fix(studio): a style set by its JavaScript name saves under its CSS name 2026-10-02 03:39:03 -07:00
Miguel Angel Simon Sierra 1a4e4dd04a fix(studio): undo of a move whose save failed stays undone and steps back no older edit 2026-10-02 03:39:03 -07:00
Miguel Angel Simon Sierra 4e1e75d7f4 fix(studio): undo takes back the first edit, and the first save never waits on media 2026-10-02 03:39:02 -07:00
Miguel Angel Simon Sierra a26f2d730e fix(studio): Studio leaves cookies out of its requests only when served from loopback 2026-10-02 03:39:01 -07:00
Miguel Angel Simon Sierra 411c7c5bda test(studio): the undo socket test needs six pinned videos under Vite and checks each undo restores 2026-10-02 03:39:00 -07:00
Miguel Angel Simon Sierra 7859d49ebe docs(studio-server): set the credentials flag before Studio loads; a mounting host injects it 2026-10-02 03:38:59 -07:00
Miguel Angel Simon Sierra b7a8f54f35 chore(studio): the fetch lint rule also catches window.fetch, globalThis.fetch and self.fetch 2026-10-02 03:38:58 -07:00
Miguel Angel Simon Sierra a6e0ec59f6 fix(studio): plan every group member before the first write so one refusal moves none
A group drag or nudge preflighted each member against the move refusal rule only. The value-at-playhead planner ran later, in the commit loop, after earlier members were already flushed to disk, so a tween with extras, eased keyframes or an unknown ease left the group half moved.

The group preflight now runs the commit's own route and keyframe plan for every member with its real offset, so any refusal refuses the whole group with nothing written and one toast attributed to the refusing member. The plan is not reused in the commit: earlier members' writes change the file under it, so each member re-plans against the fresh tween, from the same planValueEdit.
2026-10-02 03:38:58 -07:00
Miguel Angel Simon Sierra e75eb23d74 fix(studio): a rotate on a tween that turns another axis holds its angle and never turns early 2026-10-02 03:38:57 -07:00
Miguel Angel Simon Sierra 6d19a866bb fix(studio): an edit at the playhead rewrites its tween in place, so later tweens keep their times 2026-10-02 03:38:56 -07:00
Miguel Angel Simon Sierra f19f052efa test(studio): nested composition reads expect the cookie-free request studioApiFetch sends 2026-10-02 03:38:55 -07:00
Miguel Angel Simon Sierra ef37c5b62f test(studio): keep the undo-restore browser test off the network and inside its timeout 2026-10-02 03:38:54 -07:00
Miguel Angel Simon Sierra 0fa64b8aaf refactor(studio): drop the soft reload's reparse seeding, the held-element sweep covers it 2026-10-02 03:38:53 -07:00
Miguel Angel Simon Sierra d32b39c3e0 fix(studio): an undo with no project open reloads in full instead of skipping nested files 2026-10-02 03:38:53 -07:00
Miguel Angel Simon Sierra d2aa4b8941 fix(studio): freeze-frame, loudness and peak-map requests go through studioApiFetch too 2026-10-02 03:38:52 -07:00
Miguel Angel Simon Sierra 901e6d02db test(studio): a drag on a mixed tween is one write under the gesture key 2026-10-02 03:38:51 -07:00
Miguel Angel Simon Sierra 785a073a1f chore(studio): keep the carried fixes inside the comment ratchet; telemetry posts through studioApiFetch 2026-10-02 03:38:50 -07:00
Miguel Angel Simon Sierra 101ef7288d fix(studio): a resize inside one gesture writes a mixed tween once, in place, never beside it 2026-10-02 03:38:49 -07:00
Miguel Angel Simon Sierra 0121fa3dc3 test(studio): an undo is not held up by the preview's paused videos 2026-10-02 03:38:48 -07:00
Miguel Angel Simon Sierra b275921219 docs(studio-server): a host on same-origin cookies sets the Studio credentials opt-out 2026-10-02 03:38:47 -07:00
Miguel Angel Simon Sierra 375458dfab feat(studio): a host whose API needs same-origin cookies can have Studio send them 2026-10-02 03:38:46 -07:00
Miguel Angel Simon Sierra c419469114 fix(studio): an undo no longer waits behind paused preview videos for a connection 2026-10-02 03:38:45 -07:00
Miguel Angel Simon Sierra 70132fb882 fix(studio): an edit no longer reopens the live-updates stream, and thumbnails ask one at a time 2026-10-02 03:38:44 -07:00
Miguel Angel Simon Sierra eee5a34e44 fix(studio): an outside change during a drag waits for the drop to show
While a gesture mark is live in the preview, refreshPlayer takes the shadow load instead of
swapping scenes in place. A swap replaced the node under the pointer mid-drag; the shadow load
is then held by the existing gesture hold and, if the drop saved, reloaded fresh by the stale
rule, so this adds no second "released" signal.

Test: an outside change during a live drag on a preview that can swap scenes leaves the dragged
node in place, shows nothing under the pointer, and after the drop loads fresh with the dropped
position. It fails on the parent without the condition ("swapped under the pointer").

Cost case, measured in a real headless preview on the edit-accuracy bench's per-frame teleport
check. Every GSAP instant patch was forced to miss and fall back to reloadPreview during its own
commit; move and rotate of a held element (gsap.set and tl.set holds), px and xPercent
placement, r0 and r30, root and nested, at 100%:
- The gesture mark was live when the fallback asked for the reload, but had always ended by the
  time refreshPlayer ran (refreshKey is consumed in an effect after the commit settles). So the
  new condition never fired there: root previews offer no scene swap and load shadow on both
  builds, and nested previews swap on both builds.
- Worst post-release frame offset, 32 cases on this build and 32 on the parent: 0.00 px over
  76-106 frames each (release to settle). No frame was off by more than 0.5 px.
- Sensitivity: a 3 px nudge injected 50 ms after the fallback reads back as 3.00 px.
2026-10-02 03:38:43 -07:00
Miguel Angel Simon Sierra fee9a166b9 feat(studio): a host can move layers by exact film px, one undo per call
handleDomGroupMoveBy(moves) on useDomEditSession and DomEditValue moves each
selection by its delta in composition px, for Desktop's align and distribute.

It reuses the group drag's own path: a drag member per element (press-time
route, measured offset map), the delta carried to screen px through the
preview frames, then handleGsapAwareGroupPathOffsetCommit, so one shared
coalesce key gives one undo entry and GSAP members take the GSAP write.
A member that can't move refuses the call before anything is written; the
refusal rejects with its reason and shows no Studio toast (the group commit
takes refusalToast: false; telemetry stays). Zero deltas are skipped.

Tests: a pre-offset element and one under a half-scale parent move by exactly
{10,0} and {0,-5} under one group key; a half-scale parent with a 30 deg
rotated ancestor lands within the 0.001 px a translate is written with; a
GSAP member goes through the GSAP write; three refusal kinds write nothing,
leave the boxes where they were, show no toast and leave no gesture mark,
while a canvas group drag still toasts; the session hands the call to the
group commit with refusalToast: false. Each mutation (no frame mapping, no
zero skip, no restore, no teardown, no capability check, toast on refusal)
fails at least one of them.
2026-10-02 03:38:43 -07:00
Miguel Angel Simon Sierra f3de9cb2cc refactor(studio): name the map form instead of commenting it 2026-10-02 03:38:42 -07:00
Miguel Angel Simon Sierra c6eb82bc30 feat(studio): export ColorField and GradientField; a session style commit takes a map 2026-10-02 03:38:41 -07:00
Miguel Angel Simon Sierra db6920536f fix(studio): an edit stays saved when the history reply after it cannot be read 2026-10-02 03:38:40 -07:00
Miguel Angel Simon Sierra 629c894a61 fix(studio): undo pressed while an edit saves undoes that edit, not one started after the key 2026-10-02 03:38:39 -07:00
Miguel Angel Simon Sierra 64076fcda3 fix(studio): an edit on a GSAP-animated layer writes its value at the playhead into the file's tween 2026-10-02 03:38:38 -07:00
6632d99c9c fix(engine): pin capture surface scale to 1 device pixel per CSS pixel (#3782)
* fix(engine): pin capture surface scale to 1 device pixel per CSS pixel

Capture reads the browser's compositor surface (`Page.captureScreenshot`
with `fromSurface: true`, and `HeadlessExperimental.beginFrame` likewise),
and that surface is rasterized at the host display's backing scale.
`page.setViewport({ deviceScaleFactor })` goes through
`Emulation.setDeviceMetricsOverride`, which only moves
`window.devicePixelRatio` on the page side — it does not resize the
surface.

On a HiDPI host every render therefore came out at twice the composition:
a 1920x1080 composition encoded to a 3840x2160 file, and no flag could
bring it down, because the pipeline's own deviceScaleFactor was already 1.
Supersampling stacked on top of it, so `--resolution landscape-4k`
produced 7680x4320 rather than 3840x2160.

Pinning the surface with `--force-device-scale-factor=1` leaves the
deliberate supersampling path (the capture clip's `scale`, from
`resolveDeviceScaleFactor`) as the only thing that multiplies output
pixels. The flag is a no-op on Linux, where the surface is already 1x, so
CI and cloud output are unchanged.

* fix(engine): drop the capture-scale comments the ratchet rejects

---------

Co-authored-by: Leo Xu <xuliyou@bytedance.com>
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-10-02 03:30:44 -07:00
Miguel Ángel 70ed332a45 test(studio): score edits on GSAP-animated size, scale, rotation and keyframes (#4866)
* test(studio): score edits on GSAP-animated size, scale, rotation, keyframes and from/fromTo

* test(studio): settle the edit bench only once no shadow preview is waiting to be swapped in

* test(studio): end a drag at its recorded pointer-up, even when no frame painted while held

* ci(studio): run the keyframed edit accuracy cases in CI across 20 shards

* refactor(studio): split the keyframe render and stray-css checks into small helpers

* test(studio): bank the edit bench's smooth verdict as smooth, judged on the worst frame's work

* style(studio): name the edit gate's smooth count helper

* test(studio): count smooth edit cases only among the accurate ones

* test(studio): only keyframed cases wait for the preview swap, so others keep main's undo timing

* refactor(studio): keep the edit bench's keyframe check apart from the hidden preview scan

* test(studio): check the keyframed cases are in the full grid without pinning its size

* test(studio): quarantine the nudge sequence whose redo waits behind preview videos (#4889)

* test(studio): bank the 255 keyframed edit cases that pass
2026-10-02 03:26:08 -07:00
Wu ShuwenandMiguel Ángel 74e6df4370 fix(release): use portable Git paths in release guards (#3766)
* fix(release): use portable Git paths in release guards

* fix(release): drop the comment on a path git never compares

---------

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
2026-10-02 03:17:16 -07:00
Miguel Ángel cafb86a4ab Merge pull request #4896 from heygen-com/release/v0.8.111
chore: release v0.8.111
v0.8.111
2026-10-02 01:33:39 -07:00
Miguel Angel Simon Sierra 8447a55a1d chore: release v0.8.111 2026-10-02 01:32:47 -07:00
Miguel Ángel de13693179 feat(studio): a host undo key paints a still-saving move back at once (#4894)
* feat(studio): let a host's undo key paint a still-saving move back at once

* test(studio): prove the public revert from a .ts test and drop the export comment

* test(studio): a failing revert check cannot leave an edit pending, and a second revert paints nothing

* test(studio): load the package index once at collection, not inside the revert test
2026-10-02 01:16:28 -07:00
Vance Ingalls 8bf7d09bdf Merge pull request #4895 from heygen-com/release/v0.8.110
chore: release v0.8.110
v0.8.110
2026-10-02 00:56:12 -07:00
Miguel Ángel a2efe7b46d fix(studio): timeline lanes hold still while a clip drags onto a new track (#4892)
* fix(studio): timeline lanes hold still while a clip drags onto a new track

* fix(studio): name the timeline pin guards instead of commenting them
2026-10-02 00:55:41 -07:00
Vance Ingalls ec6363d3b6 chore: release v0.8.110 2026-10-02 00:31:59 -07:00
Vance IngallsandClaude Opus 5.5 70900216f0 feat(studio): audio gain on G, speed in clip names, additive selection and partner clamp (8/8) (#4821)
* feat(studio): show clip speed in the clip name as [150%] or [ramp]

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

* feat(studio): one fx badge per clip replaces the per-tool and link badges

Grey with no effects, white with any. The tooltip lists look, voice, other
fx, crop and ducking; a click opens the clip menu. The volume badge stays
when volume is off 100%.

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

* feat(studio): premiere-style audio gain dialog on G

Set Gain to, Adjust Gain by, Normalize Max Peak to, Normalize All Peaks to
and Normalize loudness to -16 LUFS, with the selection's peak amplitude read
from the peak map. Writes data-volume on every selected clip with sound as
one undo step. The clip menu's Normalize loudness becomes Audio Gain... (G).

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

* fix(studio): use a theme blue for the audio gain OK button

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

* fix(studio): keep shortcuts out of the audio gain modal; refuse while recording

Keys pressed inside an aria-modal dialog no longer reach the global hotkeys,
so Delete on the focused OK button cannot delete the clips the dialog edits.
The multi-clip write refuses during a recording like the single-clip one.

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

* fix(studio): offer link for any unlinked video and audio pair

Link no longer requires the same file and identical timing, matching Premiere.
Same-file pairs get a sync origin so a trimmed or moved audio shows its
offset; different files link without one. Merge back is disabled until the
pair is in sync. Linked moves keep the offset, and a trim only carries to a
partner whose edge is at the same time.

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

* fix(studio): shift and cmd-click add timeline clips to the selection

A modifier click on a clip replaced the selection, so a video and an audio
could never both be selected and Link never appeared. Shift, Cmd and Ctrl
now toggle the clip (and its link partners unless Alt) in the selection;
Shift no longer turns a clip click into a time-range popover.

Right-clicking a lone unlinked video or audio offers Link when exactly one
opposite-kind clip from the same file sits on another track.

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

* fix(studio): keep audio inside its partner video and make cmd-click add in every host

A modifier click passed the toggle as an argument to an optional
onSelectElement call, so hosts without that handler (desktop) never ran it.

An audio clip linked to a video, or sharing its sync origin, now stays
inside that video's span while dragged or trimmed, Alt edits included,
unless the video moves or trims in the same gesture.

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

* fix(studio): hold the drag actor at the partner-video clamp

The dragged audio's ghost followed the pointer past the video's end while
the landing stayed clamped; it now stops where the clip will land.

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

* feat(studio): preview a look on hover in the clip menu before applying it

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

* refactor(studio): trim comments the clip-link names already carry

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

* fix(studio): unlinking clears the timeline selection

After Unlink both clips stayed selected, so the next edge trim ran the
multi-select group resize and the former partner still followed. Unlink
from the menu, the shortcut or an Alt edit now leaves nothing selected.

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

* fix(sdk): keep a linked partner's end when a trim changes the grabbed duration

Copying the grabbed clip's duration to a partner that starts later pushed the
partner's end past the clip both ended on. The partner's duration is now the
shared new end minus its own start.

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

* fix(studio): keep every moving detached audio inside its video, after placement too

The containment clamp ran before collision placement and only for the
grabbed clip, so a drop pushed past an obstacle, or audio carried along in a
multi-selection, could land outside its video. The allowed shift is now the
intersection over every moving audio, and a placement outside it is
rejected. Link partners are matched within their source file.

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

* fix(studio): carry the normalize refusal and save outcomes into the gain dialog

Normalize moved from the clip menu into the Audio Gain dialog here, so the
volume-lane refusal and the saved/refused/failed checks go with it: a lane
owned clip is refused, and a failed single or multi-clip save rejects instead
of reporting success.

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

* refactor(studio): trim comments the gain and multiselect layer added

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

* refactor(sdk): split the linked partner's duration out of partnerTiming

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

* fix(sdk): refuse a linked trim whose new end would cross the partner's start

With linked video [2,8] and audio [3,8], setTiming(video, {duration: 0.5})
computed a partner duration of -0.5, skipped it, and committed the video alone,
leaving the audio outside its video. The partner edits are now planned before
any mutation; a non-positive partner duration throws ("Linked audio would start
after the new end — unlink or trim the audio first.") and can() reports
E_LINKED_PARTNER_CROSSED, so nothing is written and the undo stack is
untouched. In Studio the refusal fails the cutover (never declines into the
legacy path) and the timeline resize toast shows the reason.

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

* fix(sdk): key a linked edit's grabbed baselines by composition scope

planLinkedTiming stored each target's timing under its raw data-link id, while
linkedPartnerIds scopes link ids to their host composition. One setTiming naming
a root video and a nested video that both use lk-1 kept only the nested baseline,
so the root audio was skipped and left past its trimmed video. Baselines are now
keyed by host scope and link id, the same rule linkedPartnerIds uses, for both
the partner trim and the crossing refusal.

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

* test(sdk): run the two-scope grabbed-baseline cases on an inline composition too

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 07:19:53 +00:00
Miguel Ángel 6388845ac9 fix(studio): a hand drag moves only with its own pointer pressed (#4880)
* fix(studio): a hand drag moves only with its own pointer pressed

Chromium re-sends the last known pointer point with no button held after a
layout change. The overlay's move handler took those as drag travel, so the
element flipped between the pointer and that stale point, and the release
committed whichever came last.

* test(studio): the edit accuracy grid drops a drag after a buttonless move at the press point

Nine pathstray cases: a 180,90 drag whose last move before the release is a buttonless pointermove back at the press
point, as Chromium resends after a layout change. Main saves the element where it started; this head saves it where
the pointer let go. The frame sampler ignores that untrusted move as pointer position.

* fix(studio): keep the drag rule's comment to one line

* fix(studio): crop handles and the marquee band also ignore a buttonless move mid-gesture

* test(studio): bank the nine stray-move cases in the edit accuracy baseline

* test(studio): one crop drag helper, the band's candidate refresh in its own step, and formatting

* style(studio): wrap the pointer gesture comment under 120 characters
2026-10-02 00:08:20 -07:00
Miguel Ángel 2b495e29dd Merge pull request #4893 from heygen-com/release/v0.8.109
chore: release v0.8.109
v0.8.109
2026-10-02 00:04:05 -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
Miguel Ángel b5e3ffa6a7 fix(studio): soft reloads restore what GSAP wrote and holds from the saved files (#4871)
* fix(studio): soft reloads put a nested element's tweened style back from its own file

* fix(studio): timing edits, held elements and DOM-building scripts reload from the files

* refactor(studio): one owner settles the nested file reads for every soft reload
2026-10-01 23:24:32 -07:00
Miguel Ángel 6dcc300fd2 fix(studio): Renders dropdown options take a mouse click again (#4890)
* fix(studio): Renders dropdown options take a mouse click again

* test(studio): name a Select whose list never opens and keep both Studio layout checks running
2026-10-01 23:22:06 -07: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 Ángel df30bf8795 Merge pull request #4891 from heygen-com/release/v0.8.108
chore: release v0.8.108
v0.8.108
2026-10-01 22:21:29 -07:00