Skip to content

fix(studio): a copied clip keeps its look and motion, and a duplicated section keeps its name - #4756

Open
miguel-heygen wants to merge 5 commits into
mainfrom
fix/studio-copy-keeps-look
Open

miguel-heygen wants to merge 5 commits into
mainfrom
fix/studio-copy-keeps-look

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

What

A clip copied with Cmd+C and Cmd+V, or duplicated with Cmd+D, now looks and moves like its original. Duplicating a section no longer renames both it and its copy.

Why

  • Paste and duplicate give the copy a new id when the original's id is taken (goodbye becomes goodbye-2). The film's CSS rules and GSAP tweens name the original by that id, so the copy lost both. It drew in the default black 24 px text and never faded in. On a dark film it was invisible.
  • A duplicated section keeps its data-composition-id, which is how two instances of one sub-composition are written. At load, the runtime gives each instance a unique id (benefit-fresh__hf1, benefit-fresh__hf2) and keeps the authored one in data-hf-original-composition-id. The timeline label was built from the unique id, so both clips read "Benefit Fresh Hf1" and "Benefit Fresh Hf2".

To reproduce either bug on main: in a film with <h2 id="goodbye"> styled by #goodbye { color: ... } and tweened by tl.from("#goodbye", ...), select the clip and press Cmd+D. For the name, do the same on a clip that hosts a sub-composition through data-composition-src.

How

  • packages/studio/src/utils/clipboardLook.ts (new): for each id the paste renamed, it copies every top-level CSS rule keyed to the original id for the copy's id, using the browser's own CSS parser. Only the entries of a selector list that name the id are copied, and never one under :not(), so the copy restyles nothing else. It also copies the original's tweens through the GSAP writer. It reads scripts and styles in one pass, so a <style> written inside a script's text stays script.
  • pasteTimelineClips calls it when the clips come from the same file of the same project. Duplicate always does; paste does when the copy's project and file are the target's. The clipboard now records the project.
  • packages/parsers/src/gsapWriterAcorn.ts copyAnimationsInScript: a copy of a tween is exact or absent.
    • Only a tween at a number, written straight in the block that declares the timeline, is copied. The copy keeps the original's own argument text and moves its position by the paste's offset, so stagger, delay and ease behave the same.
    • A tween at a label or relative position, one with no position, one in a loop, one on a variable, and one inside a callback or a guard (window.go && tl.from(...)) are not copied. Their start depends on what comes before them, so a copy could not be placed exactly.
    • Copies go at the end of that block, before its return. The timeline is in scope there, and nothing that follows in its scope is pushed back. That holds when the timeline is declared at the top, inside an if or try, or inside a function.
    • A gsap.set on the original is copied right after itself.
    • This follows what split already does for its second half (duplicateCssRulesForId, splitAnimationsInScript), but runs in the browser, where paste and duplicate edit the file. The server's postcss version also covers @media and escaped ids. Sharing one owner would put postcss in the browser bundle; that is left for a follow-up.
  • packages/core/src/runtime/timeline.ts: a clip's label, and a scene's, is built from the authored composition id when the runtime renamed the instance.

Not covered, and the copy keeps today's behaviour in these cases:

  • a rule inside @media or @supports;
  • a tween not at a number, in a loop, on a variable, inside a callback or guard, or outside the timeline's own block: the copy has no such tween;
  • a clip pasted into a different file, whose ids can mean other elements there;
  • a DOM element pasted outside the timeline.

Test plan

  • Unit tests added/updated
  • Manual testing performed
  • Documentation updated (if applicable)
  • Comments follow CONTRIBUTING.md "Comments"

What I measured

On a Linux box:

  • clipboardLook.test.ts runs the pasted film's own script with real GSAP. It checks three things: the copy's rules equal the original's, the copy's tween starts where the copy lands (4 s), and the title's positionless tween still starts at 1.5 s. It fails with the carry call removed. It also fails when the copies are inserted right after the original tween, which pushes the title's tween back.
  • gsapWriter.acorn.test.ts has seven new tests:
    • the copy's text, and that it ends the timeline's block;
    • no copy of a tween at a label or with no position;
    • no copy of a tween inside a listener, a guard or a tl.call callback;
    • a copy inside the if block that declares the timeline;
    • a copy before a function's return;
    • a set's copy right after it;
    • a script with no tween on the id is left as it was.
  • clipboardLook.test.ts runs the pasted film with real GSAP in four tests. The key one mixes a stagger, a delay, a positionless tween, a label, a tween on a variable and one in a loop, inside an if block. After the paste every one of the film's own tweens starts exactly where it did. The copy of the tween at a number starts 3 s after its original, as the clip does. Its check that the film's own tweens keep their starts fails when the copies are placed right after the copied tween. The others cover selector lists, :not(), a <style> string inside a script, and another file.
  • timeline.test.ts has one new test: two instances of a scene are both named "Benefit Fresh". It fails without the fix, reading "Benefit Fresh Hf1" and "Hf2".
  • Test runs: timeline.test.ts 71 of 71, gsapWriter.acorn.test.ts 37 of 37, Studio clipboard and payload tests 35 of 35.
  • Typecheck, lint, format and the fallow audit are clean.
  • An end-to-end run in a host app using this Studio build, 3 of 3 green:
    • Cmd+D: the copy is orange at 96 px and fades in at 4 s;
    • Cmd+C then Cmd+V: the same;
    • Cmd+D on a section: both clips stay "Benefit Fresh".
  • The same run on 0.8.94 shows the copy black at 24 px with no tween, and the section names Hf1 and Hf2.

What I did NOT exercise

  • No run on macOS or Windows.
  • The Studio app itself, outside a host app.
  • A paste across files.

Before

At 5 s only the duplicate (4 to 7 s) is on screen, and it is invisible. The duplicated section and its original read "Benefit Fresh Hf2" and "Benefit Fresh Hf1".

Before: Cmd+D, the copy at 5 s is invisible

Before: Cmd+V, the pasted copy is invisible

Before: the section and its copy read Hf2 and Hf1

After

At 5 s the duplicate shows in the original's orange. Both sections read "Benefit Fresh".

After: Cmd+D, the copy at 5 s is orange like the original

After: Cmd+V, the pasted copy is orange

After: both sections read Benefit Fresh

@miguel-heygen
miguel-heygen force-pushed the fix/studio-copy-keeps-look branch from 76eede7 to 9dcd46a Compare September 30, 2026 07:44
@miguel-heygen
miguel-heygen marked this pull request as ready for review September 30, 2026 09:29

This branch has not been deployed

No deployments
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.

1 participant