Let the conversations card on the replacement MR overview collapse

Adds a chevron toggle to the Conversations card so the thread list can be closed and reopened. It starts open if any thread needs attention when the threads first load and closed otherwise, and resolving threads later does not close it. It keeps the heading and open count visible while collapsed, using the same control as the description and risk assessment cards.

The rest of the conversations stack has merged, so this now targets master.

Open:

mr258493-open

Collapsed:

mr258493-closed

Detailed context for AI agents

Why this changed

The Conversations card always shows every thread. On a merge request with many threads it pushes the rest of the overview down, so this adds a way to close it. The description card (!252999 (merged)) and the risk assessment card (!257492 (merged)) already collapse the same way, so the same control is reused for consistency.

Chain

Merged: !252481 (merged), !255057 (merged), !252997 (merged), !252998 (merged), !252999 (merged), !257511 (merged), !257492 (merged), !257832 (merged), !257879 (merged), !257871 (merged), !257872 (merged).

This MR is the last in the stack. It was rebased onto master on 2026-10-09 after !257872 (merged) merged.

This MR was first opened against !257832 (merged) as a sibling of !257879 (merged), which forked the stack. It was rebuilt on !257872 (merged)'s shape and retargeted on 2026-09-29.

What changed

discussions_panel.vue only:

  • isExpanded state, null until decided. The Apollo query's result hook sets it once, on the first result that is not loading, with isExpanded ??= hasError || openCount > 0, so the card opens when there are unresolved threads or when the query fails (so the error message is visible), and stays closed when everything is resolved or nothing is resolvable. isOpen is isExpanded ?? true, so the card is open while the first page loads. Duo review caught the failed-query case on the first push. Because of ??=, a toggle before the first result is kept, and later results (Load more, the refetch after Apply, cache updates after resolving) never change the state. It was originally a reactive fallback on openCount, but resolving the last open thread then collapsed the card and hid the Reopen thread button, which the feature spec resolves a thread from its row caught. If the first load closed the card and a later refetch fails, the card does not reopen, but the refetch in Apply still shows an alert.
  • A GlButton (tertiary, small, btn-icon) with GlAnimatedChevronLgDownUpIcon, a tooltip, aria-label and aria-expanded, in a right-aligned group with the filter control. Labels reuse the existing Collapse and Expand strings, so locale/gitlab.pot is unchanged. contentId from uniqueId('ai-overview-conversations-') links the toggle to the body through aria-controls.
  • The filter control and the Human / Agent legend render only while open (v-if="isOpen && discussions.length"), so a collapsed card is just the heading, the open count and the chevron.
  • Everything under the header (error, loading, list, empty state, Load more, Add a comment) is wrapped in one div with v-show="isOpen", so the diff is mostly re-indentation. Reviewing with whitespace changes hidden shows the real change.
  • v-show rather than v-if, so collapsing does not unmount the list or re-run the query, and the loaded pages, open reply forms and filter selection survive a close and reopen.

The card stays open while the first page loads, so the loading icon is visible. This is the ?? true default. Without it, the card started closed (openCount is 0 until data arrives) and hid the loading icon.

Spec changes

  • Three new examples under collapsing: open by default with an unresolved thread and the right ARIA attributes; closed by default with only resolved and non-resolvable threads, then opens from the toggle; collapses to the heading and open count (filter and legend gone) and re-expands from the toggle.
  • reports the error when the query fails became opens to report the error when the query fails and asserts the body is shown, not just that the text is in the DOM.
  • shows a loading icon while loading became opens to show a loading icon while loading and asserts the body is shown. It fails if isOpen does not default to open before the first result.
  • resolves an open thread (under thread actions) now also asserts the body stays shown after resolving the only open thread. It fails against the previous component.
  • The code snippets and suggestions setup no longer selects the All filter. Each fixture is a single thread: an unresolved one shows under Needs action, and a resolved one gets the All default from !257872 (merged). Rows stay in the DOM while the card is collapsed.
  • The when nothing needs action setup (added by !257872 (merged), which defaults the filter to All when nothing needs action) now expands the card first, because that fixture starts closed with the filter hidden.

Left out

  • Remembering the open or closed state between page loads. The description and risk cards do not either.

Verification performed

  • Jest: the whole ee/spec/frontend/merge_requests/ai_overview directory passes (180 examples), re-run after the rebase.
  • Prettier and ESLint clean, and the lefthook pre-push hooks (eslint, prettier, gettext, danger, secrets detection) passed on the rebased push.
  • Screenshots above retaken on GDK from the rebased branch on 2026-10-09, no JavaScript console errors. The card opened on its own with 2 unresolved threads and collapsed to its header on click.
  • 2026-10-12 fix for the card collapsing after resolving the last thread: the jest spec file passes (56 examples), and the new assertion fails against the 40fa2106 component. The feature spec file run locally (15 examples) passes :144. :35 failed once on a cold first run and passed on rerun (it passes in CI). ESLint, Prettier and the lefthook pre-push hooks were green on the push.
Edited by Marc Shaw

Merge request reports

Loading
Loading