Remove dead scrollbar gutter in the Duo AI panel

What does this MR do and why?

The Duo Agentic Chat panel showed a gap on the right edge: the chat's scrollbar and prompt input sat about 15px inboard of the panel's actual right edge.

content_container.vue wraps routed AI panel content in .panel-content-inner. That class (app/assets/stylesheets/layouts/panel.scss) sets overflow-y: auto and scrollbar-gutter: stable, reserving space for a scrollbar. The Duo Chat message list (ee/app/assets/javascripts/ai/duo_agentic_chat/components/duo_agentic_chat_view.vue) reuses the same .panel-content-inner class for its own message scroller, one level down. That produced two nested scroll containers, each reserving a scrollbar gutter. The outer one never actually scrolls when chat is the active view, so its 15px gutter was permanent dead space pushing the inner (real) scrollbar and the prompt input away from the panel edge.

Both wrappers were converted to .panel-content-inner at the same time in commit acf4007a ("Refine work item and issue panel header styles"), which is how they ended up double-reserving.

Measured locally in a 400px-wide panel, before the fix:

element offsetWidth clientWidth actually scrolls?
panel wrapper 400 385 no (scrollHeight === clientHeight)
chat message scroller 385 370 yes

The fix forces scrollbar-gutter: auto on the outer wrapper via the Tailwind arbitrary-property utility [scrollbar-gutter:auto], which overrides the SCSS scrollbar-gutter: stable because tailwind.css loads after application.css (verified in the running app). After the fix, the wrapper's clientWidth is 400 and the chat scroller spans the full width with its scrollbar flush against the panel edge.

While in there, also fixed a secondary issue: the wrapper was display: block, so its existing gl-flex-wrap gl-justify-center classes were no-ops. That forced everything nested below it into height: 100% chains. The wrapper is now a flex column (gl-flex gl-min-h-0 gl-grow gl-flex-col), so routed views can size themselves with flex grow + min-height: 0 instead of percentage heights — more robust, since WebKit has historically been unreliable resolving percentage heights against a flex item's computed height.

Screenshots

Before After
duo-panel-before duo-panel-after

How to set up and validate locally

  1. Open the Duo Chat panel from the navigation rail on any page.
  2. Look at the right edge of the panel and confirm the chat's scrollbar sits flush against it, and the prompt input reaches the edge (no gap).
  3. Resize the panel (default width, maximized, and below the lg breakpoint) and confirm the gap does not reappear.
  4. Switch to the Sessions tab and confirm it still scrolls correctly and only reserves scrollbar width when its content actually overflows.

Verification

  • Chromium, against the real Vite build of the local GDK: gap gone; the sticky "GitLab Duo" chat header still pins on scroll; the outer wrapper's scrollHeight - clientHeight is 0. Checked at default panel width, maximized, and below the lg breakpoint.
  • Sessions tab, which does rely on the wrapper to scroll, still scrolls correctly (487px of scrollable content) and only claims scrollbar width when it actually overflows.
  • WebKit 26.5 (headless, via Playwright): behavior unchanged and correct.
  • Jest: ee/spec/frontend/ai/components/content_container_spec.js and ee/spec/frontend/ai/duo_agentic_chat/components/duo_agentic_chat_view_spec.js — 111 tests pass.
  • ESLint and Prettier clean.

Notes for the reviewer

  • Out of scope: .panel-content-inner's top-shadow-on-scroll affordance uses container-type: scroll-state, which Safari does not support, so that shadow never appears in Safari. This is cosmetic and pre-existing, not touched by this MR.
Edited by Enrique Alcántara

Merge request reports

Loading
Loading