Skip to content

feat(studio): record the layer name on each history entry's target - #7586

Merged
miguel-heygen merged 3 commits into
mainfrom
dedit-history-layer
Nov 12, 2026
Merged

miguel-heygen merged 3 commits into
mainfrom
dedit-history-layer

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

What

A history entry's targets now carries an optional name: the layer name the person saw when they made the edit, which is the layers panel's label for the element (it falls back to the tag name when nothing names the element, as the panel does).

  • HistoryTarget.name?: string in studio-server. The history route accepts it on a claim's targets, trims it and caps it at 200 characters. It is never an identity: a target still needs an id, hfId or selector, and renaming the layer later moves nothing.
  • The Studio client sends it from the commit sites that hold the edited element: the GSAP script commits (the preview drag, labelled "Move layer", sent no targets at all before), the DOM edit persist path and the DOM batch commits, and the timeline's clip move and resize (persistTimelineEdit, persistTimelineBatchEdit; the name is the label the clip wears on the timeline). A shared editContextTarget(file, target) replaces two inline copies of the same object literal; saveProjectFilesWithHistory now forwards targets.

Why

An entry is labelled by what happened ("Move layer") and says nothing about which layer. A consumer that lists history cannot tell one layer from another, so it cannot group a run of the same edit on the same layer or name it ("Moved Title x6"). The name is recorded at the moment of the edit because it cannot be recovered afterwards: the layer may be renamed or deleted.

Proof

  • Server: routes/history.test.ts, "keeps a target's layer name, trimmed and capped, and never as its only identity", fails without the change (the stored target has no name) and passes with it. The history route and history suites pass: 15 files, 307 tests.
  • Timeline: timelineEditingHelpers.test.ts (single clip and batch) fails without the change and passes with it.
  • Client: useGsapScriptCommits.test.tsx, "files a gsap move under the layer it moved, by identity and by the name the person saw", fails without the change (no targets recorded) and passes with it. studioFileHistory.test.ts covers editContextTarget. Studio suites touched by the change pass (the timeline helpers, quick saves, file history, gsap commits and the nle components: 18 files, 264 tests), and tsc --noEmit is clean for studio and studio-server.
  • Ran on a Linux box from a copy of this tree; format and lint are clean on the changed files.

Before

A layer drag in Studio (a project from the repo's own fixtures, packages/studio/tests/e2e/fixtures/design-panel-qa) on main (6b602495): the history's newest entry is labelled "Move layer" and says nothing about which layer. The capture is that entry as GET /api/projects/qa/history returns it, after the drag is committed.

before: the entry has no targets

After

The same drag on this branch: the entry now carries targets with the layer's identity and the name the person saw ("Qa Tween Box"). Studio itself looks identical before and after (the window after the drag is in the same captures), because the change is in what is recorded, not in what is drawn. The capture of the entry was taken at 5774c2c9; the later commits only remove comments.

after: the entry has targets with the layer name

Not covered

  • Timeline operations that are not a move or resize (delete, paste, duplicate, visibility, asset drop, link and attribute edits) still file no target; they are not repeated manual edits a consumer would collapse.
  • The GSAP timing folds that follow a clip drag record into the same entry (same coalesce key) and add no target of their own; the clip's target comes from the drag's own claim.
  • The DOM batch path carries a name only when the patch target is a full selection; a bare patch target has no label to send.
  • Not run: the full studio suite, or a real Studio drag against a built server.

@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 88 of those

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 (8)

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: APPROVE · 0 blocker · 0 important · 0 nits · head 2041820c

This is a feature, not a fix. The Proof section still names tests that fail without the change.

Scope

Read the full diff (12 files). On the server, name is trimmed, capped at 200 characters, and stored only next to an id, hfId or selector. targetKey leaves name out, so when edits coalesce the latest name wins and the target isn't duplicated. keepTargetsOfItsFiles and revisionState still filter targets by file. On the client, editContextTarget keeps the field set of the two inline copies it replaces. saveProjectFilesWithHistory passes input through, so targets now arrives. A batch timeline target uses the same sourceFile || activeCompPath || "index.html" path as changesByPath, so addTargets keeps it. An empty targets list is dropped. The latest CI, PR captures, regression and Windows runs at this head are green.

— Genesis

@miguel-heygen
miguel-heygen added this pull request to the merge queue Nov 12, 2026
Merged via the queue into main with commit 4fad43d Nov 12, 2026
156 of 159 checks passed
@miguel-heygen
miguel-heygen deleted the dedit-history-layer branch November 12, 2026 06:01
@miguel-heygen miguel-heygen mentioned this pull request Nov 12, 2026
4 of 6 tasks
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