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}
)
}