Repository navigation
fix(studio): stop popovers and tooltips clipping at panel edges - #2890
Conversation
The Renders tab format popover rendered as an in-flow absolute panel inside the right panel, which is overflow-hidden, so it was sliced at the panel edge. Portal it to the body and position it with the shared floating-panel helper instead. The ui/Tooltip bubble clamped only its centre point to the viewport, so a wide bubble near an edge still hung off-screen (the timeline Selection tool tooltip lost 32px on the left). Clamp with the measured bubble width.
vanceingalls
left a comment
There was a problem hiding this comment.
Verdict: APPROVE — grade A — rubric CORRECT
Root-cause fit: Correctly identifies two separate defects. The Renders ? popover was an in-flow absolute right-0 w-52 inside StudioRightPanel's overflow-hidden ancestors — portal-to-body + position: fixed (via resolveFloatingPanelPosition) is the right escape. ui/Tooltip clamped only the centre point, so translate(-50%) still hung wide bubbles off-screen; measuring bubbleRef.offsetWidth in useLayoutEffect (pre-paint) and clamping via clampCentredLeft(centre, width, viewport, margin) fixes the geometry.
Claims verified (file:line at HEAD 9d2b05f2):
floatingPanel.ts:27-35—clampCentredLeftmatches PR body math; 4 unit cases pass by hand.Tooltip.tsx:88—useLayoutEffectmeasures before paint, avoids visible jump;clampedXused at:108.RenderQueue.tsx:157-235— portal todocument.body,aria-expanded/aria-describedby={panelId}bridge portal boundary; Escape handler at:175-183.- File-list and +114/-31 match.
Adversarial findings:
- nit —
FORMAT_PANEL_SIZE.height=150(RenderQueue.tsx:143) is estimated; if guidance text grows past ~150px the flip decision will pick "bottom" incorrectly near viewport bottom. Comment acknowledges. Low risk today. - nit — Portaled popover positions once and does not reposition on panel/window scroll (comment at
RenderQueue.tsx:171). Hover-lived + trigger inflex-shrink-0header makes this mostly safe; keyboard-open + scroll could drift. - nit —
Tooltip.tsx:113putsaria-describedbyon adisplay: contentsspan wrapper; AT reading the focused child may not surface the tooltip description. Pre-existing shape; consider hoisting to child on next pass. - nit —
z-[200]on both portals; verify no Modal/Dropdown sits lower next time layers are audited.
CI state: Preflight, Lint, Format, Studio load smoke, Regression, CodeQL green on the latest run. Typecheck + Build + Test still pending at review time; older run had unrelated Producer/Smoke:global-install failures (stale).
Suggested next step: Land after Typecheck + Build clear. File a follow-up for scroll-reposition + panel-height measurement if popover content grows.
— Review by Via
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Small, well-scoped fix — the root-cause diagnosis is exactly right (in-flow absolute right-0 inside an overflow-hidden ancestor for the format popover; centre-clamp-then-translate leaves a wide bubble hanging off the edge for the tooltip), and both fixes match patterns the codebase already uses (portal + resolveFloatingPanelPosition for FormatInfoTooltip, clampCentredLeft measuring the bubble in useLayoutEffect for Tooltip). The two-commit pre-paint dance in Tooltip.tsx:75-77 is React 18-safe (setState inside useLayoutEffect triggers a synchronous re-render before browser paint), and the four clampCentredLeft unit tests cover the boundary cases cleanly. The 21+ existing <Tooltip> consumers gain corrected clamping with no API change, so this can't regress its callers.
Nits
-
RenderQueue.tsx:139FORMAT_PANEL_SIZE = { height: 150 }is load-bearing for the flip decision AND the top clamp — the inline comment at:137-138says "only the flip decision uses the height, and the clamp keeps the panel on screen either way," butfloatingPanel.ts:59clamps using the estimated height, so if actual > estimated and the anchor is near the viewport bottom, the real bottom can overflow. Current content sits around 130-160px, so this is safe today. Worth a// estimate must stay >= actual panel heightline in the comment to prevent someone growing the content past the estimate without noticing. -
RenderQueue.tsx:227renders the portal atleft: -9999, top: -9999on the frame betweensetOpen(true)and theuseLayoutEffectat:166computing position. Pre-paint viauseLayoutEffect, so no visible flash — but the pattern is un-remarked, and a reader has to piece it together. A one-liner (// Off-screen until useLayoutEffect at :166 sets position — same commit, no paint at -9999) would make the intent obvious. -
Tooltip.tsx:89computesclampedXon every render regardless ofvisible. Whenvisible=false,bubbleWidth=0and the computation is discarded. Not a perf issue, but hoisting inside thevisible && createPortal(...)branch at:104would tighten the render tree. -
floatingPanel.ts:48— the horizontal clampMath.max(margin, viewport.width - panel.width - margin)foldspanel.width > viewport.width - 2*margininto "stick to left margin" via the outerMath.max, mirroringclampCentredLeft's edge behavior (floatingPanel.test.ts:18-20). Consistent, but a one-line docstring onresolveFloatingPanelPositioncalling out this edge case (like the one onclampCentredLeft:25-28) would help. -
The
Tooltipmeasurement path (two-commituseLayoutEffectdance) doesn't have an integration test — the 4 new unit tests coverclampCentredLeftin isolation, but not the wider Tooltip flow. If a future React upgrade changesuseLayoutEffectscheduling, only manual QA catches the regression. Ahappy-domtest assertingbubbleWidth > 0afteract(() => render(...))with the tooltip visible would document the invariant.
Questions
- The PR body flags
propertyPanelFont.tsx(font dropdown,left-0 right-0) as "not covered here, separate fix" for potential bottom-edge clipping. Is that scheduled as a follow-up, or landing implicitly when someone else touches the file?
What I didn't verify
- The 21+
<Tooltip>consumers in a real browser — trusting Miguel's PR-body sweep across speed menu, shortcuts panel, snap/grid, ease-curve dropdown. - The bottom-edge clipping on the font dropdown (called out as out of scope).
- Current-SHA CI —
Tests on windows-latest,Build,Test,Typecheck,CLI smoke,Producer: integration testsare still pending on9d2b05f2a; the twofailentries onSmoke: global install/Producer: matrix.lane testsare on an older run id (30502359506), appear stale.
— Review by Rames D Jusso
…en-com#2890) The Renders tab format popover rendered as an in-flow absolute panel inside the right panel, which is overflow-hidden, so it was sliced at the panel edge. Portal it to the body and position it with the shared floating-panel helper instead. The ui/Tooltip bubble clamped only its centre point to the viewport, so a wide bubble near an edge still hung off-screen (the timeline Selection tool tooltip lost 32px on the left). Clamp with the measured bubble width.
What
Two Studio overlays were rendered cut off:
?popover — the format guidance box was sliced at the right panel's left edge (~143px of it, at a 1512px window).Why
Both are the same defect class: an overlay positioned without accounting for what will clip it.
absolute top-full right-0 w-52panel. Two ancestors inStudioRightPanelareoverflow-hidden, so anything extending past the panel is cut. Anchoredright-0on a control near the panel's left column, the 208px box always reached outside the panel.ui/Tooltipclamped only the bubble's centre point to the viewport, then centred the bubble on it withtranslate(-50%). A bubble wider than twice its distance from the edge still hangs off-screen.How
FormatInfoTooltiprenders in a portal todocument.bodywithposition: fixed, placed by the existingresolveFloatingPanelPositionhelper (same pattern as the color picker inpropertyPanelColor). Hover grace,aria-describedby, and Escape-to-dismiss behaviour are unchanged; the portaled panel carries its own pointer-enter/leave so moving onto it keeps it open.clampCentredLeftinfloatingPanel.tsclamps a centred bubble by its measured width, andui/Tooltipmeasures the bubble inuseLayoutEffect(before paint, so there is no visible jump) and uses it.Test plan
Verified in a live Studio (
bun run dev, port 5190) against a real project: opened the Renders tab and hovered the Format?(full box renders, nothing clipped), and hovered the timeline Selection tool (bubble clamped toleft: 8).Swept the other in-flow popovers by measuring their rects against every
overflow-hiddenancestor in the real DOM: speed menu, shortcuts panel, snap/grid popover, and the ease-curve dropdown are all unclipped and stay in the viewport. Context menus and the color picker were already portaled.Not covered: the font dropdown (
propertyPanelFont.tsx) isleft-0 right-0so it cannot overhang horizontally, but it may still be clipped at the panel's bottom edge when the field sits low in the inspector. Untested here, separate fix.clampCentredLeft; full studio suite green: 3118 passed)