fix(copilot): keep sidebar mode beside host content instead of overlapping it

RO
2mo ago
+228-19

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.

recording

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:

  • A containing block on <body> — if the host sets transform (commonly translateZ(0)), perspective, or will-change on <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.
  • Viewport-filling shells — apps sized to the viewport (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 a transform / perspective / will-change — commonly a translateZ(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.py on http://localhost:8000.

2. Serve this host page from a static server (not file://) and open it:

<!doctype html>
<html>
  <head><meta charset="utf-8" /></head>
  <!-- transform on <body> is the trigger (a common GPU hint) -->
  <body style="min-height: 100vh; transform: translateZ(0);">
    <div style="height: 250vh; padding: 1rem; background: linear-gradient(#111, #333); color: #fff;">
      Host content — scroll down
    </div>
    <script src="http://localhost:8000/copilot/index.js"></script>
    <script>
      localStorage.setItem("chainlit-copilot-displayMode", "sidebar");
      window.mountChainlitWidget({ chainlitServer: "http://localhost:8000", displayMode: "sidebar", opened: true });
    </script>
  </body>
</html>

3. Observe the sidebar.

  • Before this PR: the sidebar leaves a white gap on the right, stretches to the full document height, and scrolls away with the page.
  • After this PR: the sidebar is pinned to the right edge, is viewport-height, stays fixed while scrolling, and the host content shrinks beside it.

Viewport-filling variant (hostRoot)

Give the host a full-screen shell instead of normal-flow content:

<div id="root" style="position: fixed; inset: 0">…</div>
  • Without hostRoot, the sidebar overlaps it — a body margin can't shrink a 100vw / absolute-inset layout.
  • Add hostRoot: "#root" to mountChainlitWidget, 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 with margin-right.

For viewport-filling apps, a new opt-in hostRoot config lets integrators name their app root; when set, that element's width is constrained to calc(100vw - sidebarWidth) instead of the body margin. Everything is saved and restored on close / mode-switch / unmount.

window.mountChainlitWidget({
  chainlitServer: "...",
  displayMode: "sidebar",
  hostRoot: "#root" // optional: for 100vw / absolute-inset app shells
});

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

  • Suspends transform, perspective, and will-change on <body> while the sidebar is open, keeping the fixed panel viewport-anchored.
  • Restores all modified styles on close, mode switch, or unmount, even if a hostRoot node is swapped, removed, or appears after open—dropping the body-margin fallback as soon as a host resolves so space isn't reserved twice.

New Features

  • Adds optional hostRoot config for viewport-filling shells (e.g. maps) that ignore body margins.
  • When set, constrains that element's width to calc(100vw - sidebarWidth) instead of the body margin.