Conversation
…pping it In sidebar display mode, reserve space for the panel with body margin-right and suspend the containing-block properties the host may set on <body> (transform, perspective, will-change) while the sidebar is open — all restored on close. Those properties make <body> the containing block for the fixed sidebar, re-anchoring it to the margin-shrunk body: the sidebar gets pushed (white gap) and, on tall pages, stretches to the document height and scrolls with the page. Suspending them keeps the sidebar a true viewport-fixed panel and leaves the host's own layout untouched. Co-Authored-By: GitHub Copilot <noreply@github.com>
…pps in sidebar mode Viewport-filling hosts (100vw / position: absolute inset shells like maps and dashboards) ignore the body margin, so the sidebar overlapped them. Add an optional hostRoot widget config: when set, sidebar mode constrains that element's width to calc(100vw - sidebarWidth) instead of nudging the body margin, restored on close. No behavior change when it is unset. Co-Authored-By: GitHub Copilot <noreply@github.com>
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
Share a single getHostRoot helper and re-query the host on cleanup instead of reusing a cached node; use overflow-x: clip (not hidden) so the host does not become a scroll container; toggle the drag transition on whichever element is reserved so host-width drags animate too. Co-Authored-By: GitHub Copilot <noreply@github.com>
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
…idth before transition Cleanup now restores the exact node it styled using that node's captured styles, and undoes the body-margin reservation whenever the body fallback was used, so a hostRoot that is swapped or removed while open no longer leaves the wrong node or the body in a stale state. Also flush the host width before enabling its transition: Chromium cannot interpolate width from auto to calc(), so it stuck the host at its pre-open width and never shrank. The body margin animates from 0 and is unaffected. Co-Authored-By: GitHub Copilot <noreply@github.com>
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…malization The browser rewrites inline style values: translateZ(0) becomes translateZ(0px) and calc(100vw - 400px) becomes calc(-400px + 100vw). Assert the restored transform against the normalized value and check the hostRoot width via its bounding rect instead of the raw calc string. Co-Authored-By: GitHub Copilot <noreply@github.com>
…ssion reserveSpace resolves the host fresh each call to handle SPA node swaps, but cleanup only restored the node captured at open, leaving a swapped-in host stuck at width: calc(100vw - Xpx). Track each styled host node in a ref map and restore them all on close. Co-Authored-By: GitHub Copilot <noreply@github.com>
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
… fallback The host path never touches body margin-right or transition, so restoring them unconditionally on close clobbered any host-app updates made while the sidebar was open. Restore them only when no host node was constrained. Co-Authored-By: GitHub Copilot <noreply@github.com>
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
hosts.size doubled as the body-fallback signal, so a hostRoot that disappeared mid-session (map non-empty, body margin written) skipped the margin restore and left the page shifted. Add a usedBodyFallback ref set on each body-branch write and reset per session; cleanup restores all host nodes always and body margin/transition only when the fallback was used. Co-Authored-By: GitHub Copilot <noreply@github.com>
Co-Authored-By: GitHub Copilot <noreply@github.com>
There was a problem hiding this comment.
3 issues found across 4 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="cypress/e2e/copilot/spec.cy.ts">
<violation number="1" location="cypress/e2e/copilot/spec.cy.ts:227">
P3: This test covers only the open state of the hostRoot path. The PR's key behavior is restore-on-close/unmount ('Everything is restored on close' in useSidebarResize.ts, and rollback via styledHosts), but nothing asserts that after closing or unmounting, #test-host-root gets its original inline width/overflowX/transition back. A regression in the styledHosts restore logic would ship silently, unlike the analogous transform-restore case the neighboring test does cover. Add a close step asserting the host width and margin right are restored, mirroring the transform test.</violation>
<violation number="2" location="cypress/e2e/copilot/spec.cy.ts:244">
P3: The negative assertion `to.not.equal('400px')` is too weak: it passes for any other value, including a wrongly applied body margin of '500px' or a stale margin, so it would not catch a regression where the widget falls back to the body-margin path with a non-default width. Since the hostRoot path never touches the body margin, assert that it is untouched instead.</violation>
</file>
<file name="libs/copilot/src/hooks/useSidebarResize.ts">
<violation number="1" location="libs/copilot/src/hooks/useSidebarResize.ts:69">
P2: When `hostRoot` is unavailable at open and appears later, `reserveSpace` keeps the fallback body margin while also constraining the host. Clear the fallback margin when switching to a resolved host, while retaining the original value for cleanup.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| const reserveSpace = useCallback( | ||
| (width: number) => { | ||
| const host = getHostRoot(); | ||
| if (host) { |
There was a problem hiding this comment.
P2: When hostRoot is unavailable at open and appears later, reserveSpace keeps the fallback body margin while also constraining the host. Clear the fallback margin when switching to a resolved host, while retaining the original value for cleanup.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At libs/copilot/src/hooks/useSidebarResize.ts, line 69:
<comment>When `hostRoot` is unavailable at open and appears later, `reserveSpace` keeps the fallback body margin while also constraining the host. Clear the fallback margin when switching to a resolved host, while retaining the original value for cleanup.</comment>
<file context>
@@ -19,14 +20,80 @@ interface UseSidebarResizeReturn {
+ const reserveSpace = useCallback(
+ (width: number) => {
+ const host = getHostRoot();
+ if (host) {
+ rememberHost(host);
+ host.style.width = `calc(100vw - ${width}px)`;
</file context>
| mountCopilotWidget({ | ||
| displayMode: 'sidebar', | ||
| opened: true, | ||
| hostRoot: '#test-host-root' |
There was a problem hiding this comment.
P3: This test covers only the open state of the hostRoot path. The PR's key behavior is restore-on-close/unmount ('Everything is restored on close' in useSidebarResize.ts, and rollback via styledHosts), but nothing asserts that after closing or unmounting, #test-host-root gets its original inline width/overflowX/transition back. A regression in the styledHosts restore logic would ship silently, unlike the analogous transform-restore case the neighboring test does cover. Add a close step asserting the host width and margin right are restored, mirroring the transform test.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cypress/e2e/copilot/spec.cy.ts, line 227:
<comment>This test covers only the open state of the hostRoot path. The PR's key behavior is restore-on-close/unmount ('Everything is restored on close' in useSidebarResize.ts, and rollback via styledHosts), but nothing asserts that after closing or unmounting, #test-host-root gets its original inline width/overflowX/transition back. A regression in the styledHosts restore logic would ship silently, unlike the analogous transform-restore case the neighboring test does cover. Add a close step asserting the host width and margin right are restored, mirroring the transform test.</comment>
<file context>
@@ -189,6 +189,62 @@ describe('Copilot', { includeShadowDom: true }, () => {
+ mountCopilotWidget({
+ displayMode: 'sidebar',
+ opened: true,
+ hostRoot: '#test-host-root'
+ });
+
</file context>
| ); | ||
| }); | ||
| cy.document().should((doc) => { | ||
| expect(doc.body.style.marginRight).to.not.equal('400px'); |
There was a problem hiding this comment.
P3: The negative assertion to.not.equal('400px') is too weak: it passes for any other value, including a wrongly applied body margin of '500px' or a stale margin, so it would not catch a regression where the widget falls back to the body-margin path with a non-default width. Since the hostRoot path never touches the body margin, assert that it is untouched instead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cypress/e2e/copilot/spec.cy.ts, line 244:
<comment>The negative assertion `to.not.equal('400px')` is too weak: it passes for any other value, including a wrongly applied body margin of '500px' or a stale margin, so it would not catch a regression where the widget falls back to the body-margin path with a non-default width. Since the hostRoot path never touches the body margin, assert that it is untouched instead.</comment>
<file context>
@@ -189,6 +189,62 @@ describe('Copilot', { includeShadowDom: true }, () => {
+ );
+ });
+ cy.document().should((doc) => {
+ expect(doc.body.style.marginRight).to.not.equal('400px');
+ });
+ });
</file context>
Summary
In the Copilot widget's sidebar display mode, the fixed panel could overlap host content or leave a white gap on some host pages. This PR fixes that: the sidebar now stays pinned as a true viewport-height panel beside the host content, and it also handles viewport-filling host apps (maps/dashboards) through an opt-in config.
Root cause
The widget reserves space with
body { margin-right }, which only works while the fixed sidebar is anchored to the viewport and the host content flows inside the body's content box. Two host situations break that:<body>— if the host setstransform(commonlytranslateZ(0)),perspective, orwill-changeon<body>, that element becomes the containing block for the fixed sidebar, which re-anchors to the margin-shrunk body: the sidebar gets pushed (leaving a white gap) and, on tall pages, stretches to the full document height and scrolls with the page.100vw/position: absolute; inset: 0, e.g. a full-screen map) don't live in the body's content box, so a body margin can't shrink them and the sidebar overlaps.How to reproduce
The bug only appears when the host page makes
<body>a containing block for fixed elements (via atransform/perspective/will-change— commonly atranslateZ(0)GPU hint) or when the host fills the viewport. A plain host page won't show it.1. Start a Chainlit server (any app), e.g.
chainlit run app.pyonhttp://localhost:8000.2. Serve this host page from a static server (not
file://) and open it:3. Observe the sidebar.
Viewport-filling variant (
hostRoot)Give the host a full-screen shell instead of normal-flow content:
hostRoot, the sidebar overlaps it — a body margin can't shrink a100vw/ absolute-inset layout.hostRoot: "#root"tomountChainlitWidget, and the app reflows into the reduced width beside the sidebar.Fix
While the sidebar is open, on
<body>we suspend the containing-block properties (transform,perspective,will-change) so the fixed sidebar stays anchored to the viewport, and reserve space withmargin-right.For viewport-filling apps, a new opt-in
hostRootconfig lets integrators name their app root; when set, that element's width is constrained tocalc(100vw - sidebarWidth)instead of the body margin. Everything is saved and restored on close / mode-switch / unmount.There is no DOM reparenting and no style copying, so the host's own layout and native scrollbar are preserved.
Summary by cubic
Fixes the Copilot widget sidebar so it stays beside host content instead of overlapping it or leaving a white gap.
Bug Fixes
transform,perspective, andwill-changeon<body>while the sidebar is open, keeping the fixed panel viewport-anchored.hostRootnode is swapped or removed while open, and undoes the body margin only when the fallback was used.New Features
hostRootconfig for viewport-filling shells (e.g. maps) that ignore body margins.calc(100vw - sidebarWidth)instead of the body margin.Written for commit c3d80d4. Summary will update on new commits.