Skip to content

fix(studio): pressing Shift no longer re-renders the timeline - #7298

Merged
miguel-heygen merged 2 commits into
mainfrom
dperf/timeline-shift
Nov 6, 2026
Merged

miguel-heygen merged 2 commits into
mainfrom
dperf/timeline-shift

Conversation

@miguel-heygen

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

Copy link
Copy Markdown
Collaborator

What

Pressing Shift anywhere in an app that shows the Studio timeline no longer re-renders the timeline. The crosshair cursor over the timeline while Shift is held stays as it was.

Why

useTimelineShiftModifier kept Shift in React state in the timeline's provider, so every press and every release rendered the whole timeline tree, whatever had focus. On a repo fixture in Studio that is 1 commit and 45 components for each press and each release; with this change it is 0 and 0. The app embedding the timeline pays it on every capital letter typed in any other panel. The state only ever chose a cursor class.

Related work

None found: no open issue or PR on this hook.

How

The hook now toggles a data-shift-held attribute on the timeline's container, and the container's class reads it with data-[shift-held]:cursor-crosshair. shiftHeld is gone from the provider and the meta builders. React does not own that attribute, so its renders leave it alone. Razor mode keeps its own crosshair, and losing window focus still clears Shift.

Test plan

  • Unit tests added/updated: useTimelineShiftModifier.test.tsx counts commits with a React Profiler: 0 across Shift down, another key, Shift up, and blur, while the attribute follows Shift.
  • Manual testing performed: Studio's dev server in headless Chromium on a repo fixture (packages/producer/tests/chat), pointer over the timeline, Shift pressed and released 5 times, commits counted through React's devtools hook, cursor read with getComputedStyle. Before and after both read default > crosshair > default, so the data-[shift-held]:cursor-crosshair variant lands in the served CSS. A production bundle was not run.
  • Documentation updated (if applicable): nothing to update.
  • Comments follow CONTRIBUTING.md "Comments".

fails without the fix: useTimelineShiftModifier.test.tsx "Shift marks the timeline for its cursor without rendering it", with Shift back in React state, ends AssertionError: expected 5 to be 1.

A timeline that remounts while Shift is held (the empty state swapping to the container) has no attribute until the next press. Before this change the state survived the remount. I left that as is: the cursor corrects itself on the next press.

Before

Before: 1 commit and 45 components per Shift key event

After

After: 0 commits and 0 components per Shift key event, crosshair unchanged

Size

About 70 changed lines. It stands alone: this is the only open change in this repo for this hook, and nothing else here can carry it.

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 108 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)

Unstable (2)

  • seqplainkeys-none-px-r0-root-z100: tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo false, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true / tracking 0.06, pressJump 0, drop 0, reload 0, render 0.03, renderKey -, undo true, teleport true
  • seqfastkeys-none-px-r0-root-z100: tracking 0.06, pressJump 0.05, drop 0, reload 0, render 0.02, renderKey -, undo false, teleport true / tracking 0.07, pressJump 0.05, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true / tracking 0.07, pressJump 0.05, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true

@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.

Requesting changes at a0f4da35. The approach looks right, but four gates fail on this head.

Blockers

  1. No "fails without the fix:" line. This is a fix PR, and the body needs a line starting fails without the fix: that names the test. useTimelineShiftModifier.test.tsx › "Shift marks the timeline for its cursor without rendering it" would qualify: on main the hook takes no ref and keeps Shift in state, so data-shift-held is never set and every press commits. Please state that, along with the failure you saw.
  2. CI › Format (and the preview-regression preflight): oxfmt --check flags packages/studio/src/player/components/useTimelineShiftModifier.test.tsx. Running bun run format should fix it.
  3. Comments: useTimelineShiftModifier.ts goes from 0/21 to 2/23 comment lines, and the ratchet only allows a file's share to go down. Drop the JSDoc and let the hook's name and the void signature carry it, or move the reason into the PR body.
  4. PR captures: the change touches packages/studio with .tsx files and is over 20 lines, so the ## No visible change exemption doesn't apply and the body needs ## Before and ## After with captures. This also closes the open manual-testing box. The one thing this can break is the crosshair while Shift is held over the timeline. A before/after of hovering the timeline with Shift held would prove the Tailwind data-[shift-held]:cursor-crosshair variant actually lands in the built CSS. The unit test only checks the class string and the attribute, not the cursor.

Looks right

  • shiftHeld is fully removed: nothing in packages/ reads it any more, and the types would catch a missed reader.
  • containerRef is the same ref that setContainerRef sets on meta.containerProps in Timeline.tsx. The hook reads container.current when the event fires, so a container that mounts after the effect still gets the attribute.
  • Razor mode keeps cursor-crosshair as before, and a window blur still clears Shift.
  • The Profiler test is a good proof that pressing Shift causes no commits.

Not blocking: TimelineView swaps the container for the empty state when elements.length === 0. If the container remounts while Shift is held, it has no attribute until the next Shift press. Before this change the state survived the remount. It's an edge case and fine to leave, but worth knowing about.

@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.

Approving at b70a6c4c. All four blockers from my last review are fixed.

  1. "Fails without the fix" line: present. It names useTimelineShiftModifier.test.tsx › "Shift marks the timeline for its cursor without rendering it", which with Shift back in React state ends AssertionError: expected 5 to be 1. That fits the test: 5 window events, each committing once.
  2. Format: the test file is reformatted, and CI (format included) and the preview-regression preflight pass.
  3. Comments: the hook's JSDoc is gone, and the Comments workflow passes.
  4. Captures: ## Before and ## After are real Studio screenshots on the chat fixture, labelled with the measured numbers: 1 commit and 45 components per Shift event before, 0 and 0 after, with the cursor reading default > crosshair > default both times. The getComputedStyle reading in the manual test shows the data-[shift-held] variant lands in the served CSS, which was my open question. PR captures passes on its re-run after the body edit. The earlier failed run came before the edit.

The code change since a0f4da35 is formatting plus the dropped comment, with no logic change. The remount edge case is now written up in the body and accepted. CI on this head passed: CI, Comments, regression, preview-regression, Windows render verification, PR captures, CodeQL and Player perf.

@miguel-heygen
miguel-heygen added this pull request to the merge queue Nov 6, 2026
Merged via the queue into main with commit 66cbabc Nov 6, 2026
124 of 125 checks passed
@miguel-heygen
miguel-heygen deleted the dperf/timeline-shift branch November 6, 2026 06:36
SanHsien added a commit to SanHsien/hyperframes that referenced this pull request Nov 6, 2026
…view

Adopts heygen-com#7294 (line_unread findings carry the measured frames), heygen-com#7271
(blur mutes discrete callbacks instead of undoing them), heygen-com#7293 (the
filter tier's svg keeps the last child), heygen-com#7292 (a mover holding a fixed
child stays sharp), heygen-com#7288 (auto motion blur draws with the filter tier)
and heygen-com#7298 (Shift no longer re-renders the timeline). The fork's seek
recovery in captureMotionBlur stays. Defers heygen-com#7134 (no packages/share)
and heygen-com#7295 (needs the heygen-com#7080 crop pipeline). Watermark advances to
66cbabc.

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
Co-authored-by: Vance Ingalls <vance@heygen.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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