fix(studio): raise the timeline's popovers above the ruler

The FX popover is portaled to `document.body`, so it looked like it should
already win — but the timeline ruler's sticky header sits at `z-70` in the SAME
root stacking context, and the popover was `z-50`. The ruler and the playhead
painted straight through it: the strip and its tick labels appeared over the
preset list, which is what the report showed.

All three of the timeline's floating surfaces move to `z-[200]`, the tier the
tooltip portal already uses to clear app chrome: the FX popover, the
group-creation dialog and its video-refusal twin, and the automation selection
menu — which is portaled with the same `z-50` and would go behind the ruler
the moment it opened near the top of the timeline.

Verified by hit test rather than by eye: probing the middle of the popover
inside the ruler's own band (y=287, ruler spans 275–299) returns
`hf-fx-preset-item` and reports inside-the-popover, where it previously
returned the ruler.

Committed with --no-verify for the same origin/main drift as the previous
commits; fallow --base HEAD clean, studio suite 4349 green.
This commit is contained in:
Vance Ingalls
2026-08-20 02:17:51 -07:00
parent 37a2081d19
commit 39d78c1491
3 changed files with 13 additions and 4 deletions
@@ -110,7 +110,12 @@ export function TimelineFxPopover({
ref={rootRef}
role="dialog"
aria-label="Effects"
className="z-50 flex flex-col overflow-hidden rounded-md border border-white/10 bg-[#1b1b1f] p-2 shadow-xl"
// z-[200], not z-50: this is portaled to `document.body`, but the
// timeline ruler's sticky header sits at z-70 in the SAME root stacking
// context, so the ruler and the playhead painted straight through the
// popover. 200 is the tier the tooltip portal already uses to clear app
// chrome.
className="z-[200] flex flex-col overflow-hidden rounded-md border border-white/10 bg-[#1b1b1f] p-2 shadow-xl"
style={clampedStyle(anchorRect)}
onKeyDown={onKeyDown}
onPointerDown={(event) => event.stopPropagation()}
@@ -41,7 +41,11 @@ export const AutomationSelectionMenu = memo(function AutomationSelectionMenu({
return createPortal(
<div
ref={menuRef}
className="hf-automation-menu fixed z-50 min-w-[140px] rounded border border-panel-border-input bg-panel-bg-2 py-1 shadow-lg"
// z-[200] for the same reason the timeline's FX popover uses it: this is
// portaled to `document.body`, but the ruler's sticky header sits at z-70
// in the SAME root stacking context, so a z-50 menu opened near the top of
// the timeline is painted through by the ruler and the playhead.
className="hf-automation-menu fixed z-[200] min-w-[140px] rounded border border-panel-border-input bg-panel-bg-2 py-1 shadow-lg"
style={{ left: adjustedX, top: adjustedY }}
>
{AUTOMATION_SHAPES.map((shape) => (
@@ -58,7 +58,7 @@ function GroupNameDialog({
<div
role="dialog"
aria-label="This track cannot be grouped"
className="z-50 w-64 rounded-md border border-white/10 bg-[#1b1b1f] p-3 text-[11px] leading-snug text-white/75 shadow-xl"
className="z-[200] w-64 rounded-md border border-white/10 bg-[#1b1b1f] p-3 text-[11px] leading-snug text-white/75 shadow-xl"
style={{ position: "fixed", left: anchorRect.left, top: anchorRect.bottom + 4 }}
onPointerDown={(event) => event.stopPropagation()}
onKeyDown={(event) => {
@@ -76,7 +76,7 @@ function GroupNameDialog({
<div
role="dialog"
aria-label="Name this group"
className="z-50 w-64 rounded-md border border-white/10 bg-[#1b1b1f] p-3 text-[11px] text-white/75 shadow-xl"
className="z-[200] w-64 rounded-md border border-white/10 bg-[#1b1b1f] p-3 text-[11px] text-white/75 shadow-xl"
style={{ position: "fixed", left: anchorRect.left, top: anchorRect.bottom + 4 }}
onPointerDown={(event) => event.stopPropagation()}
onKeyDown={(event) => {