fix(copilot): correct composer popover positioning and stacking in the widget shadow DOM - #3025
Open
RoyBA wants to merge 5 commits into
Open
fix(copilot): correct composer popover positioning and stacking in the widget shadow DOM#3025RoyBA wants to merge 5 commits into
RoyBA wants to merge 5 commits into
Conversation
RoyBA
requested review from
asvishnyakov,
hayescode and
sandangel
as code owners
August 26, 2026 11:12
Contributor
There was a problem hiding this comment.
All reported issues were addressed across 4 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
The command, favorite, and mode picker popovers rendered their content inline. In the widget's floating mode the chat panel is itself a transformed Radix popper, which becomes the containing block for the fixed-positioned popovers and breaks floating-ui's scale compensation under host zoom/font-size, throwing them off-screen. Portal each popover into cl_shadowRootElement (the convention the shared ui/* primitives already use) and set an explicit z-index on each picker's content so Radix copies it onto the popper wrapper, keeping them above the z-50 chat. This matches how ui/popover, dropdown, select set z-50 on their own content and avoids a global wrapper override. Co-Authored-By: GitHub Copilot <noreply@github.com>
RoyBA
force-pushed
the
fix/copilot-popover-positioning
branch
from
August 26, 2026 11:49
d20929c to
3209114
Compare
Collaborator
|
@RoyBA Any chance for tests on this one? |
Add tests for the three composer pickers verifying each popover is portaled into cl_shadowRootElement and carries the z-[51] stacking class that keeps it above the chat surfaces. Co-Authored-By: GitHub Copilot <noreply@github.com>
Contributor
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Address review: use attachShadow instead of a plain div and assert the popover lands in the encapsulated shadow tree (not the light DOM), add a standalone document.body fallback test, centralize the ResizeObserver/scrollIntoView shims in setup-tests, and move shadow-host cleanup into afterEach. Co-Authored-By: GitHub Copilot <noreply@github.com>
Contributor
There was a problem hiding this comment.
All reported issues were addressed across 5 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
Address review: move cleanupShadowHosts into setup-tests' global afterEach so mountShadowHost consumers can't leak the DOM host or the cl_shadowRootElement global, removing the per-spec afterEach boilerplate. Co-Authored-By: GitHub Copilot <noreply@github.com>
Contributor
Author
@dokterbob Added unit tests for all three composer pickers |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
In the Copilot widget, the message-composer popovers — tools picker (
CommandPopoverButton),ModePicker, andFavoriteButton— could render far off-target (and, once portaled, behind the chat) when the widget is embedded in a host page that scales it. This fixes both the positioning and the stacking, scoped to the widget's shadow DOM. The standalone Chainlit app is unaffected.How to reproduce
html { font-size: 10px }(common in some design systems). The widget's own text now renders tiny and hard to read.customCssUrl:A transformed host root reproduces the same thing without
zoom— e.g.body { transform: translateZ(0) }(orfilter/perspective/will-change: transform), which is common on real hosts.Root causes
Two compounding, shadow-DOM-specific issues:
zoom/transform/ rootfont-size. The popovers rendered theirPopoverContentinline (no Portal). In floating mode the whole chat panel is itself a Radix popper whose wrapper carries atransform, so it becomes the containing block for the popovers'position: fixedcontent. floating-ui can't compensate for the combined host scale + panel transform, so the computed offset drifts — and the drift grows with the zoom factor, pushing the popover off-screen.z-indexonto[data-radix-popper-content-wrapper]as an inline style. These pickers set noz-index, so the wrapper inlinesautoand loses to the widget'sz-50chat surfaces.Fix
<PopoverPortal container={window.cl_shadowRootElement}>— the convention the sharedfrontend/src/components/ui/*primitives already use. This moves the content out of the transformed/zoomed panel, so position is computed against the shadow root, not a scaled containing block. In the standalone app that global isundefined, so Radix falls back todocument.body(unchanged behavior).z-[51]on each picker's own content, so Radix copies it onto the popper wrapper (matching thez-50-on-content convention of the other poppers) and it stays above thez-50chat. Scoped per-component — no global rule, no!important, no effect on other poppers.Files changed
CommandPopoverButton.tsxModePicker.tsxFavoriteButton.tsxfrontend/tests/{CommandPopoverButton,ModePicker,FavoriteButton}.spec.tsx(tests)Testing
cl_shadowRootElementand carries thez-[51]stacking class.pnpm test→ 38/38.pnpm lint,pnpm format-check,pnpm type-checkpass.zoomand non-default rootfont-size, in light and dark themes.