feat(studio): sync origin, out-of-sync repair, linked selection toggle and grouped clip menu (7/8) - #4820
Conversation
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
Reviewed the #4820 delta at exact ca563f775fcd9f0adba2c468f538f1dca6d6d037 against stacked #4818 dddeeddfd677618b68b0aa2c5abf81ac19910c55. Two correctness holds are inline: a one-sided cut loses the uncut source partner's sync origin; and SDK Slip into Sync can write a lower-precedence trim attribute, report zero offset, yet leave playback unchanged. The out-of-sync badge menu also has only a horizontal viewport adjustment; near the bottom edge its actions can render off-screen (OutOfSyncBadge.tsx:33–41), a UI follow-up. Hosted Comments is red on seven raised-comment-share files (the full job log names them), separately blocking the stack. #4818 remains held below this PR. I did not run local tests or a browser, and took no merge/release action. — Jerrai
| */ | ||
| export function relinkSplitHalves(doc: Document, rightHalfIds: readonly string[]): void { | ||
| const taken = new Set(takenLinkIds(doc)); | ||
| renameShared(doc, rightHalfIds, MEDIA_LINK_ATTR, taken); |
There was a problem hiding this comment.
relinkSplitHalves remints a right half's data-sync-origin even if only one member of an unlinked video/audio source pair is cut. This is reachable from Studio's razor path: useRazorSplit.ts expands only data-link members (and can split a single unlinked clip), then the server calls this helper on the right-half IDs. If video and audio share origin lk-1, unlink them and cut only the audio, the new right audio gets lk-2 while the uncut video stays lk-1; findSyncPartner returns null and the new half loses its out-of-sync badge/Move/Slip actions. The added server test cuts both members, so it cannot catch this. Preserve the source origin for a one-sided cut or otherwise map both source halves and test that case.
| if (mediaStart === null) throw new Error(`slipping ${id} into sync would start before its file`); | ||
| const legacy = | ||
| !pair.own.hasAttribute("data-media-start") && pair.own.hasAttribute("data-playback-start"); | ||
| const name = legacy ? "data-playback-start" : "data-media-start"; |
There was a problem hiding this comment.
When an authored clip has both data-playback-start and data-media-start, this chooses to write data-media-start because it exists. But packages/parsers/src/mediaDuration.ts:19–22 and core runtime readMediaStart prioritize data-playback-start; the write does not change playback. For example, video and audio source peers with equal timeline start, audio data-playback-start="0" and data-media-start="0"; move the audio later by 0.5 s, then Slip into Sync. This action writes only data-media-start="0.5", the SDK's offset reader reports zero, but playback continues at the old data-playback-start="0" and stays late. Please use the actual playback-precedence attribute consistently (or reconcile/remove the overridden alias), with a both-attributes regression that checks rendered source timing rather than only the SDK offset.
ca563f7 to
9314861
Compare
Edit accuracy: 953 passing here, 953 on the base branchThe gate passes. Quarantined, measured but not gated (0) Unstable (1)
|
9314861 to
c02dae4
Compare
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
Re-reviewed exact c02dae4228460a446228a75934277e0fafec7561 against current #4818. The prior two code holds are repaired: a one-sided cut keeps the original sync origin so its uncut video/audio partner remains discoverable; Slip into Sync now reads playback's in-point precedence and writes data-playback-start when authored, with corresponding regression assertions. Comments and SDK checks pass. I did not run a live Studio/browser or one-sided-cut end-to-end test.
This is not an approval: current-head File size check and Studio and player captures are red, and no per-PR evidence waiver was routed. Please clear the file-size gate and supply required Before/After captures or explicitly waive the capture evidence for this PR; recheck the live head after that. The older CHANGES_REQUESTED state and the lower #4818 stack holds still block landing. No merge/release action. — Jerrai
c02dae4 to
0595ada
Compare
0595ada to
fc17241
Compare
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
At exact fc172418abe4561eb1a8ea53c5c2c855b9873047, the previously reviewed one-sided sync-origin and slip-precedence repairs remain in place; the current head now passes the file-size and Comments gates (I am not repeating the prior red status). The SDK and Studio hosted tests ran green. I found no new confirmed code hold in this bounded re-review, but Studio and player captures ran and failed because the PR body still lacks Before/After media and no per-PR waiver was provided. No approval yet; my earlier CHANGES_REQUESTED state continues until this visual evidence or explicit waiver arrives. I did not run live Studio/audio or merge/release. — Jerrai
fc17241 to
bcad9b8
Compare
bcad9b8 to
e9f8992
Compare
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
Reviewed #4820 at e9f8992e against #4818 367fd138. findSyncPartner and same-origin pairing now use nearest inline/file composition scope, with negative cases for reused link IDs; playback-start precedence also remains aligned with the SDK. No new #4820-specific blocker found. The parent #4818 still has an SDK ambiguous-ID isolation hold in my current-head review, so the complete stack is not ready. This layer’s required Studio/player captures check is FAILED without Before/After evidence or waiver; edit-accuracy and Comments pass. No local test or playback. — Jerrai
e9f8992 to
d4a11e6
Compare
d4a11e6 to
b63293e
Compare
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
Re-reviewed this exact #4818-based layer: the previously identified one-sided sync-origin and playback-start precedence repairs remain in the preserved 75-file layer patch, and nearest composition scope prevents reused link IDs crossing inline/file hosts. The only concurrent main overlap adds the shared typing-target guard and test to the snap key without dropping the layer changes. Captures and current code checks pass; pending checks and other reviewers' holds remain separate. — Jerrai
b63293e to
d03ecc5
Compare
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
Exact d03ecc53 rebase review: all 75 layer paths and patch hunks match the old approved parent→head delta. The sole concurrent overlap is Studio's public index.ts: new-parent WebMCP exports coexist with this layer's separate audio-meter export. The prior one-sided relink and playback-start sync precedence repairs remain in the layer. This approves the layer code only; #4815's viewport failure and hosted checks still gate the stack. No merge/release action. — Jerrai
540f5b8 to
856c757
Compare
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
The rebased #4820 layer has the same 75 changed paths and the same addition/deletion hunks as the previously approved layer (only hunk offsets move around the new #4818 remote-URL fix). The one-sided split repair remains in the exact-head source; I also read the new parent’s serialized remote path/query identity and its collision tests. CI is still pending. This code approval does not merge or release. — Jerrai
… 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>
…nk 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>
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>
…'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>
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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
…ed pairs data-sync-origin survives Unlink; split gives right halves a fresh origin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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>
…th 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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pairs Core gains findSyncPartner, the DOM pairing shared by the SDK and the CLI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nt 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>
…tom edge Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
f5e1b46 to
a8a9f43
Compare
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
Fresh review of the rebased layer at exact a8a9f431382490a00335658a66c26d86f64e0a24, now based on merged #4818 (0559be0b). I independently compared its complete parent→head patch with the previously approved #4820 layer (3283d701→f5e1b467): 25 commits, 75/75 paths, identical statuses and +2,046/−253 line totals, with zero normalized-hunk differences or missing patches. At the new head, one-sided splits retain the source sync origin and Slip into Sync writes data-playback-start when it takes precedence. This approval covers the unchanged layer, not CI completion or an authorization to enqueue/merge. Hosted checks were still running, with no failing check displayed at click time; no local tests were run. — Jerrai
Summary
Premiere-style sync for linked pairs, plus a grouped clip menu.
data-sync-originrecords where the audio belongs against its video. Out-of-sync badges offer Move into Sync and Slip into Sync, and a Linked Selection toggle sits in the timeline toolbar. The clip menu is ordered into Time, Sound, Picture, Clipboard and Delete groups (host items from #4784 stay on top), and speed edits fan out to every link member in one undo step.Changes
OutOfSyncBadgewith Move/Slip into Sync. GroupedClipContextMenuwith a measured height. Link/unlink items name the partner. Linked speed edits (linkedSpeedEdits). The audio parent tick is replaced by the data-link chain badge.lk-Nlinks and re-mint sync origins.syncOffset,moveIntoSync,slipIntoSync.hyperframes timelinerows reportsyncOffsetFramesand print out-of-sync.data-sync-originin hyperframes-core.Testing
Static gates on the layer tip:
bun install,bun run build,tsc --noEmitfor every touched package (0 errors),oxlintandoxfmt --checkon files changed vs main (clean),gen:skills-manifest --check(in sync),scripts/comment-ratchet.mjs(ok).cloud/downloadpasses alone.studioServer.stampOnOpenpassed 1 of 2 runs alone on the same tree: it sometimes catches an atomic-write.tmpfrom fix: readers never see an empty or half-written project file #4777.Notes
moveIntoSyncon a linked clip unlinks it, and slip isn't capped at the media end.🤖 Generated with Claude Code
Before
After
Out-of-sync badge (-15) with Move into Sync and Slip into Sync; Linked Selection toggle in the toolbar.