From 8ce3fd8b32e183dfead36ddba2584329b11913ca Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Sun, 30 Aug 2026 14:07:06 -0700 Subject: [PATCH] fix(native-chat): keep agent responses selectable (#17437) Co-authored-by: Merge Sim --- .../NativeChatMessageList.test.tsx | 52 +++++++++++++++++++ .../native-chat/NativeChatMessageList.tsx | 20 ++++--- 2 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 src/renderer/src/components/native-chat/NativeChatMessageList.test.tsx diff --git a/src/renderer/src/components/native-chat/NativeChatMessageList.test.tsx b/src/renderer/src/components/native-chat/NativeChatMessageList.test.tsx new file mode 100644 index 00000000000..50dd54d8dc1 --- /dev/null +++ b/src/renderer/src/components/native-chat/NativeChatMessageList.test.tsx @@ -0,0 +1,52 @@ +// @vitest-environment happy-dom + +import '@testing-library/jest-dom/vitest' + +import { cleanup, render, screen } from '@testing-library/react' +import { afterEach, describe, expect, it, vi } from 'vitest' +import type { NativeChatLiveSession } from './use-native-chat-live-session' +import { NativeChatMessageList } from './NativeChatMessageList' + +afterEach(cleanup) + +const session: NativeChatLiveSession = { + messages: [ + { + id: 'assistant-1', + role: 'assistant', + blocks: [{ type: 'text', text: 'Selectable agent response.' }], + timestamp: 1, + source: 'transcript' + } + ], + status: 'ready', + sessionId: 'session-1', + agent: 'codex', + hasMore: false, + loadingEarlier: false, + loadEarlier: vi.fn(), + readPhase: 'ready' +} + +describe('NativeChatMessageList assistant messages', () => { + it('keeps prose selectable and places non-selectable controls after it', () => { + render( + + ) + + const prose = screen.getByText('Selectable agent response.') + const row = prose.closest('.group') + const copyButton = screen.getByRole('button', { name: 'Copy message' }) + const controls = copyButton.parentElement + + expect(row).toHaveClass('select-text') + expect(controls).toHaveClass('select-none', 'pointer-events-none', 'mt-1') + expect(controls).not.toHaveClass('absolute') + expect(prose.compareDocumentPosition(controls!)).toBe(Node.DOCUMENT_POSITION_FOLLOWING) + }) +}) diff --git a/src/renderer/src/components/native-chat/NativeChatMessageList.tsx b/src/renderer/src/components/native-chat/NativeChatMessageList.tsx index 4a261af1d8f..f2030362a07 100644 --- a/src/renderer/src/components/native-chat/NativeChatMessageList.tsx +++ b/src/renderer/src/components/native-chat/NativeChatMessageList.tsx @@ -68,9 +68,7 @@ function ImageAttachmentRefs({ blocks }: { blocks: NativeChatBlock[] }): React.J ) } -/** Inline controls for an agent message (mobile AgentControls parity): copy the - * message's prose, and scroll so this message's top aligns to the viewport top. - * Reveals on hover / keyboard focus like the prior copy affordance. */ +/** Footer controls for an agent message: copy its prose or align it to the viewport top. */ function AgentControls({ markdown, onScrollToTop, @@ -243,19 +241,12 @@ function MessageRow({
- {showControls ? ( - - ) : null} {markdown ? ( ) : null} {tools.length > 0 ? : null} + {showControls ? ( + + ) : null}
) }