From a498f0cc5faba0e2f17c4cceadb629f218a31edd Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Fri, 31 Jul 2026 02:24:01 +0200 Subject: [PATCH] refactor(client): unify tool row disclosure styling in a shared module Extract the hover-reveal chevron and expanded panel spacing into disclosure.ts and align every tool activity row on the same layout: h-5 header, my-1 rhythm, full-width click target, and a 200ms chevron rotation. Rows rendered through ProgressText now stretch across the message column (the absolute content layer was shrink-wrapping to the text), and the copy buttons in thinking and output panels anchor to the row instead of the panel corner. --- .../Chat/Messages/Content/AgentHandoff.tsx | 10 ++- .../Chat/Messages/Content/CodeAnalyze.tsx | 2 +- .../Chat/Messages/Content/Parts/BashCall.tsx | 8 ++- .../Messages/Content/Parts/ExecuteCode.tsx | 8 ++- .../Content/Parts/PendingSkillCall.tsx | 2 +- .../Chat/Messages/Content/Parts/Reasoning.tsx | 13 ++-- .../Messages/Content/Parts/SubagentCall.tsx | 2 +- .../Chat/Messages/Content/Parts/Thinking.tsx | 6 +- .../Parts/__tests__/ReasoningCompact.test.tsx | 62 +++++++++++++++++++ .../Parts/__tests__/ThinkingButton.test.tsx | 45 ++++++++++++++ .../Chat/Messages/Content/ProgressText.tsx | 12 ++-- .../Content/ToolOutput/OutputRenderer.tsx | 52 ++++++++-------- .../__tests__/OutputRenderer.test.tsx | 24 +++++++ .../Chat/Messages/Content/WebSearch.tsx | 19 +++--- .../Content/__tests__/ProgressText.test.tsx | 34 +++++++++- .../Content/__tests__/WebSearch.test.tsx | 25 +++++++- .../Chat/Messages/Content/disclosure.ts | 4 ++ 17 files changed, 273 insertions(+), 55 deletions(-) create mode 100644 client/src/components/Chat/Messages/Content/Parts/__tests__/ReasoningCompact.test.tsx create mode 100644 client/src/components/Chat/Messages/Content/Parts/__tests__/ThinkingButton.test.tsx create mode 100644 client/src/components/Chat/Messages/Content/ToolOutput/__tests__/OutputRenderer.test.tsx create mode 100644 client/src/components/Chat/Messages/Content/disclosure.ts diff --git a/client/src/components/Chat/Messages/Content/AgentHandoff.tsx b/client/src/components/Chat/Messages/Content/AgentHandoff.tsx index d884d5a893..16fa332c3d 100644 --- a/client/src/components/Chat/Messages/Content/AgentHandoff.tsx +++ b/client/src/components/Chat/Messages/Content/AgentHandoff.tsx @@ -5,6 +5,7 @@ import { Clipboard, CheckMark, TooltipAnchor } from '@librechat/client'; import type { TMessage } from 'librechat-data-provider'; import type { MouseEvent } from 'react'; import MessageIcon from '~/components/Share/MessageIcon'; +import { toolPanelSpacingClassName } from './disclosure'; import { useLocalize, useExpandCollapse } from '~/hooks'; import { useAgentsMapContext } from '~/Providers'; import { cn } from '~/utils'; @@ -64,7 +65,7 @@ const AgentHandoff: React.FC = ({ name, args: _args = '' }) = : localize('com_ui_copy_to_clipboard'); return ( -
+
, + FloatingThinkingBar: () => null, + useInViewport: () => ({ ref: { current: null }, inViewport: true }), +})); + +jest.mock('~/components/Messages/Content/CopyButton', () => ({ + __esModule: true, + default: () => , +})); + +jest.mock('lucide-react', () => ({ + Lightbulb: () => , + ChevronDown: ({ className }: { className?: string }) => ( + + ), +})); + +describe('ReasoningCompact', () => { + it('reveals its own Thoughts chevron instantly on row hover or focus', () => { + render(); + + const button = screen.getByRole('button', { name: 'Thoughts' }); + const chevron = screen.getByTestId('thoughts-chevron'); + const row = button.closest('.group\\/reasoning-compact'); + + expect(row).toHaveClass('mb-1', 'mt-1'); + expect(button.parentElement).not.toHaveClass('my-1.5'); + expect(button).toHaveClass('group/disclosure'); + expect(chevron).toHaveClass( + 'opacity-0', + 'group-hover/disclosure:opacity-100', + 'group-focus-within/disclosure:opacity-100', + ); + expect(chevron).toHaveClass('transition-transform'); + expect(chevron.className).not.toContain('transition-opacity'); + }); + + it('removes the extra top margin when Thoughts follows a tool', () => { + render(); + + const row = screen + .getByRole('button', { name: 'Thoughts' }) + .closest('.group\\/reasoning-compact'); + + expect(row).toHaveClass('mb-1', 'mt-0'); + expect(row).not.toHaveClass('mt-1'); + }); +}); diff --git a/client/src/components/Chat/Messages/Content/Parts/__tests__/ThinkingButton.test.tsx b/client/src/components/Chat/Messages/Content/Parts/__tests__/ThinkingButton.test.tsx new file mode 100644 index 0000000000..e352ba8a3f --- /dev/null +++ b/client/src/components/Chat/Messages/Content/Parts/__tests__/ThinkingButton.test.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { ThinkingButton } from '../Thinking'; + +jest.mock('jotai', () => ({ + ...jest.requireActual('jotai'), + useAtomValue: () => 'text-base', +})); + +jest.mock('~/hooks', () => ({ + useLocalize: () => (key: string) => key, +})); + +jest.mock('~/components/Messages/Content/CopyButton', () => ({ + __esModule: true, + default: ({ className }: { className?: string }) => ( +
{needsTruncation && (