mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
fix(native-chat): keep agent responses selectable (#17437)
Co-authored-by: Merge Sim <sim@local>
This commit is contained in:
co-authored by
Merge Sim
parent
ba5f33402f
commit
8ce3fd8b32
@@ -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(
|
||||
<NativeChatMessageList
|
||||
session={session}
|
||||
isWorking={false}
|
||||
expandSignal={false}
|
||||
fontScale={1}
|
||||
/>
|
||||
)
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
@@ -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({
|
||||
<div
|
||||
ref={rowRef}
|
||||
className={cn(
|
||||
'group relative max-w-full text-sm leading-relaxed text-foreground',
|
||||
'group relative max-w-full select-text text-sm leading-relaxed text-foreground',
|
||||
// Reasoning is the agent thinking aloud — quieter, italic, like an aside.
|
||||
isReasoning && 'border-l-2 border-border/60 pl-3 italic text-muted-foreground',
|
||||
isSystem && 'text-xs text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{showControls ? (
|
||||
<AgentControls
|
||||
markdown={markdown}
|
||||
onScrollToTop={scrollToTop}
|
||||
className="absolute -top-8 right-0 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100"
|
||||
/>
|
||||
) : null}
|
||||
<ImageAttachmentRefs blocks={prose} />
|
||||
{markdown ? (
|
||||
<CommentMarkdown
|
||||
@@ -267,6 +258,13 @@ function MessageRow({
|
||||
/>
|
||||
) : null}
|
||||
{tools.length > 0 ? <NativeChatToolRun blocks={tools} expandSignal={expandSignal} /> : null}
|
||||
{showControls ? (
|
||||
<AgentControls
|
||||
markdown={markdown}
|
||||
onScrollToTop={scrollToTop}
|
||||
className="pointer-events-none mt-1 -mb-5 w-fit select-none opacity-0 transition-opacity group-hover:pointer-events-auto group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:opacity-100"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user