Skip to content

feat(studio): sync origin, out-of-sync repair, linked selection toggle and grouped clip menu (7/8) - #4820

Merged
vanceingalls merged 25 commits into
mainfrom
aov/07-sync-origin-menu-groups
Oct 2, 2026
Merged

vanceingalls merged 25 commits into
mainfrom
aov/07-sync-origin-menu-groups

Conversation

@vanceingalls

@vanceingalls vanceingalls commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Premiere-style sync for linked pairs, plus a grouped clip menu. data-sync-origin records 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

  • core: sync origin, offset math and move/slip into sync.
  • studio: detach, link and keep-sound cutout write a sync origin that unlink keeps. Linked Selection toggle (on by default). Link groups share a label colour (theme tokens). OutOfSyncBadge with Move/Slip into Sync. Grouped ClipContextMenu with 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.
  • studio-server: freeze-frame and split halves mint lk-N links and re-mint sync origins.
  • sdk: syncOffset, moveIntoSync, slipIntoSync.
  • cli: hyperframes timeline rows report syncOffsetFrames and print out-of-sync.
  • skills: data-sync-origin in hyperframes-core.

Testing

Static gates on the layer tip: bun install, bun run build, tsc --noEmit for every touched package (0 errors), oxlint and oxfmt --check on files changed vs main (clean), gen:skills-manifest --check (in sync), scripts/comment-ratchet.mjs (ok).

  • Scoped vitest: core 3815, sdk 573, studio 6374, studio-server 999. All pass.
  • cli 3956 pass; 2 upstream tests are timing-flaky under the parallel run. cloud/download passes alone. studioServer.stampOnOpen passed 1 of 2 runs alone on the same tree: it sometimes catches an atomic-write .tmp from fix: readers never see an empty or half-written project file #4777.

Notes

  • SDK moveIntoSync on a linked clip unlinks it, and slip isn't capped at the media end.
  • Part of the audio-on-video stack. See the bottom PR for the overview.

🤖 Generated with Claude Code

Before

before

After

after

Out-of-sync badge (-15) with Move into Sync and Slip into Sync; Linked Selection toggle in the toolbar.

@vanceingalls
vanceingalls added this pull request to stack #4822 October 1, 2026 01:24

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/sdk/src/engine/syncTiming.ts Outdated
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";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Edit accuracy: 953 passing here, 953 on the base branch

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (0)

Unstable (1)

  • seqab-none-px-r0-nested-z100: tracking 0.07, pressJump 0, drop 0, reload 0, render 0.03, undo false, teleport true / tracking 0.07, pressJump 0, drop 0, reload 0, render 0.03, undo true, teleport true / tracking 0.07, pressJump 0, drop 0, reload 0, render 0.03, undo true, teleport true

@vanceingalls
vanceingalls force-pushed the aov/07-sync-origin-menu-groups branch from 9314861 to c02dae4 Compare October 1, 2026 06:39

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@vanceingalls
vanceingalls force-pushed the aov/07-sync-origin-menu-groups branch from c02dae4 to 0595ada Compare October 1, 2026 08:22
@vanceingalls
vanceingalls force-pushed the aov/07-sync-origin-menu-groups branch from 0595ada to fc17241 Compare October 1, 2026 09:47

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@vanceingalls
vanceingalls force-pushed the aov/07-sync-origin-menu-groups branch from fc17241 to bcad9b8 Compare October 1, 2026 18:56
@vanceingalls
vanceingalls force-pushed the aov/07-sync-origin-menu-groups branch from bcad9b8 to e9f8992 Compare October 1, 2026 20:01

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@vanceingalls
vanceingalls force-pushed the aov/07-sync-origin-menu-groups branch from e9f8992 to d4a11e6 Compare October 1, 2026 22:27
@vanceingalls
vanceingalls force-pushed the aov/07-sync-origin-menu-groups branch from d4a11e6 to b63293e Compare October 1, 2026 23:32

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@vanceingalls
vanceingalls force-pushed the aov/07-sync-origin-menu-groups branch from b63293e to d03ecc5 Compare October 2, 2026 00:10

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@vanceingalls
vanceingalls force-pushed the aov/07-sync-origin-menu-groups branch 3 times, most recently from 540f5b8 to 856c757 Compare October 2, 2026 02:17

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

vanceingalls and others added 23 commits October 2, 2026 05:43
… 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>
@vanceingalls
vanceingalls force-pushed the aov/07-sync-origin-menu-groups branch from f5e1b46 to a8a9f43 Compare October 2, 2026 05:43

@jerrai-bot-heygen jerrai-bot-heygen left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@vanceingalls
vanceingalls added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit 45b8e17 Oct 2, 2026
86 checks passed
@vanceingalls
vanceingalls deleted the aov/07-sync-origin-menu-groups branch October 2, 2026 06:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants