Skip to content

feat(studio): cancel element picking with Escape and show hover labels - #6900

Merged
jrusso1020 merged 1 commit into
heygen-com:mainfrom
Muzzy5150:feat/studio-picker-escape-hover-label
Nov 7, 2026
Merged

jrusso1020 merged 1 commit into
heygen-com:mainfrom
Muzzy5150:feat/studio-picker-escape-hover-label

Conversation

@Muzzy5150

Copy link
Copy Markdown
Contributor

What

Studio's element picker now cancels with Escape when focus is in the Studio chrome, restores the selection that existed before pick mode began, and shows a compact identifier beside the hovered preview element.

The identifier uses tag#id, then tag.first-class, then the tag name alone. It stays inside the visible preview/viewport, follows preview zoom and scrolling, ignores pointer input, and clears on cancellation, selection, iframe replacement, and unmount.

Why

The preview runtime already handled Escape inside its iframe and emitted pick-mode-cancelled, but useElementPicker had no Studio-window keyboard path and did not remember the pre-pick selection. It also ignored the existing validated element-hovered messages. As a result, Escape depended on which frame held focus, candidate selection could survive cancellation, and nested targets had no visible identity.

Untouched upstream at 47fe68562f39d91b4d9280dff41ae43d67805fae reproduced all three gaps: the initial regression run had 12 passing tests and 3 failures (Studio Escape remained active, no hover label was rendered, and cancellation retained the candidate instead of the original selection).

Related work

Fixes #5585.

#4727 changes the runtime's human-readable element label priority; this change does not alter that label and only adds optional class metadata for the picker identifier.

How

  • Keep the pre-pick selection in the existing picker hook and route both Studio Escape and runtime cancellation through one restoration path.
  • Attach the Studio key listener only while pick mode is active, leave dialog/editor-owned Escape events alone, and use the existing runtime enable/disable control messages.
  • Consume element-hovered only from the active validated preview, reject malformed geometry, and ignore stale replaced-frame messages.
  • Add optional className to picker element info. The runtime refreshes hover geometry after preview scroll/resize so the host label remains aligned.
  • Render the label as bounded textContent in Studio's document with theme tokens and pointer-events: none; no hover state is written to project source.
  • Transfer active pick mode across primary iframe reloads and zoomed iframe changes without introducing a second selection model.

Before

The highlighted target had no visible identifier.

Before: highlighted picker target without an identifier

After

The same target shows h1#hero-title; Escape restores the prior selection.

After: highlighted picker target with h1 hero-title identifier

Test plan

  • Unit tests added/updated
  • Manual/browser testing performed
  • Documentation updated
  • Comments follow CONTRIBUTING.md "Comments"
  • bun run --cwd packages/studio test src/hooks/useElementPicker.test.ts src/hooks/elementPickerHoverLabel.test.ts src/hooks/elementPickerHoverLabel.browser.test.ts (32 passed)
  • bun run --cwd packages/core test src/runtime/picker.test.ts (52 passed)
  • bun run --cwd packages/core test (262 files, 5,645 passed)
  • bun run --filter '*' typecheck
  • bun run lint
  • bun run format:check
  • bun run build
  • bunx fallow audit --base upstream/main --fail-on-issues (no changed-file issues)

The full Studio run completed 8,235 tests successfully. Its existing useTimelineGroupEditing GSAP backup test still fails with Unknown encoding: base64url; the identical failure reproduces at the untouched upstream SHA. One unrelated filesystem-watch timing case also flaked during the patched full run and passed immediately when rerun alone. The picker-focused, browser, typecheck, lint, format, build, and full Core gates are green.

Compatibility

Existing iframe Escape handling and normal element picks remain in place. className is optional on the shared picker info type, so older runtime messages still fall back to a tag-only identifier. Source patching, selection trust, and zoom iframe behavior are preserved, and the change adds no dependency or persisted project state.

AI assistance

Codex assisted with repository review, implementation, regression tests, and command execution. I reviewed the complete diff and validated the behavior and results above.

@mintlify

mintlify Bot commented Oct 31, 2026

Copy link
Copy Markdown

Preview approval required

This pull request comes from a fork, so Mintlify won't build a preview until someone with write access approves commit 004ba47.

Approve preview

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed at 004ba4763187d351da64087a7f1a8a00202c49d4.

Cancellation restores the selection captured when picking began, and hover labels render outside the preview. Switching the active iframe disables the previous picker before enabling the next.

No blocking issue found in the selection restoration, message validation or cleanup paths. Label animation frames and listeners are released on clear/dispose/unmount. Existing source-patching behavior is retained.

Read the picker session and hover-label implementation, runtime additions and their callers. The standard package configuration ran useElementPicker.test.ts and elementPickerHoverLabel.test.ts: 31 passed. A fresh independent source pass found no blocker. No real-browser label placement or iframe navigation claim.

Need: yes · Size: ok · Reuse: existing runtime protocol, picker API and source patching · Simpler: already minimal · Structure: fine
Risk: medium — picker session and iframe lifecycle.

— hf-review

Verdict: APPROVE
Reasoning: Escape restoration and hover feedback are implemented through the existing picker contract with explicit lifecycle cleanup.

@jrusso1020
jrusso1020 added this pull request to the merge queue Nov 7, 2026
Merged via the queue into heygen-com:main with commit f004622 Nov 7, 2026
98 checks passed
SanHsien added a commit to SanHsien/hyperframes that referenced this pull request Nov 7, 2026
…-com#7336-heygen-com#7358

Adopt heygen-com#7304 (without CLI renderDiagnostic, needs heygen-com#6725), heygen-com#7331, heygen-com#7332, heygen-com#7334,
heygen-com#7337, heygen-com#7336, heygen-com#7339, heygen-com#7289, heygen-com#5594, heygen-com#6900, heygen-com#7322, heygen-com#7342, heygen-com#7343, heygen-com#7350 (onProgress
only), heygen-com#7352, heygen-com#7348, heygen-com#7344, heygen-com#7353, heygen-com#7356 and heygen-com#7355. Defer heygen-com#5708 (needs heygen-com#6911) and
heygen-com#7345 (READ_VERSION 4 vs 7); heygen-com#7307 not applicable. Re-read 14 moved pins, pin 6
new PRs (monitored 126). Make checkReportFile module-private and suppress the
cross-package closeTestChrome duplicate for fallow.

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
Co-authored-by: SophiaHuangCc <129631224+SophiaHuangCc@users.noreply.github.com>
Co-authored-by: Dante <duanjl.china@gmail.com>
Co-authored-by: Muzzy5150 <muzzy5150yt@gmail.com>
Co-authored-by: ump45nose <52391318+ump45nose@users.noreply.github.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.

Element picker: Esc cancels, and the hovered element shows its tag and class

2 participants