fix(chat): always show copy and fork under a finished agent turn (#40474)

This commit is contained in:
Kelvin Amoaba
2026-11-09 20:39:58 +00:00
committed by GitHub
parent 387fe35bfb
commit fe1620cdf5
6 changed files with 7 additions and 12 deletions
@@ -21,7 +21,7 @@ export function NativeChatAgentControls({
}): React.JSX.Element {
const t = useTranslate()
return (
// Hover-only row chrome: find skips it rather than counting what the mouse happens to show.
// Row chrome, not transcript: find skips it.
<div data-native-chat-find-skip className={cn('flex items-center gap-1', className)}>
{/* A visual line means nothing pasted outside Orca, so the copy leaves it out. An image-only
response has nothing to copy, so it has no Copy. */}
@@ -56,7 +56,7 @@ export type NativeChatMessageListHandle = {
readerAtEnd: () => boolean
/** Bring a find match into view, clear of the find bar; the reader stops following the latest. */
revealFindMatch: (match: Range, bar: DOMRectReadOnly | null) => void
/** What the row drawing this message copies from its hover button; null when none draws it. */
/** What the row drawing this message copies from its Copy button; null when none draws it. */
messageCopyText: (messageId: string) => string | null
/** Open a subagent's activity where it sits in the conversation and bring it into view. */
revealSubagent: (agentId: string) => void
@@ -76,16 +76,11 @@ export function NativeChatAssistantMessageRow({
!message.agentStart &&
!inSubagentSection &&
isRootAgentJournalItem(message)
// A row whose fork is opening keeps its controls up so the spinner stays visible.
const revealOnHover =
fork?.pendingItemId === message.id
? 'mt-1 w-fit select-none'
: 'mt-1 w-fit select-none transition-opacity can-hover:pointer-events-none can-hover:opacity-0 group-hover:pointer-events-auto group-hover:opacity-100 [.group:has(:focus-visible)_&]:pointer-events-auto [.group:has(:focus-visible)_&]:opacity-100'
return (
<div
ref={rowRef}
// The context menu offers Copy message only where the hover controls offer Copy.
// The context menu offers Copy message only where the row's controls offer Copy.
data-native-chat-message-id={showControls && words.length > 0 ? message.id : undefined}
data-native-chat-message-tone={isSystem ? 'faint' : undefined}
className={cn(
@@ -126,7 +121,7 @@ export function NativeChatAssistantMessageRow({
) : null
}
className={cn(
revealOnHover,
'mt-1 w-fit select-none',
// They hang into the gap below; a section keeps them inside its border instead.
!inSubagentSection && '-mb-5'
)}
@@ -55,7 +55,7 @@ export type MessageRowProps = {
activeTurnIsWorking?: boolean
/** This row's tool run is the turn's last, so it is the one still live. */
trailingRun?: boolean
/** Hover controls would overlap the next row. */
/** Response controls would overlap the next row. */
continuesTurn?: boolean
/** In a turn: whether this row holds the response's actions (its last words, else its last images). */
endsResponse?: boolean
@@ -9,7 +9,7 @@ import type { NativeChatContextMenuTarget } from './native-chat-context-menu-tar
import type { NativeChatRewindSurface } from '../../messages/actions/use-native-chat-rewind-host'
function copyText(text: string): void {
// Best-effort, as the hover copy buttons are: the clipboard can reject when unfocused.
// Best-effort, as the row Copy buttons are: the clipboard can reject when unfocused.
window.api.ui.writeClipboardText(text).catch(() => {})
}
@@ -15,7 +15,7 @@ const chatFindHighlights = createDomTextSearchHighlights({
const TRANSCRIPT_COLUMN_SELECTOR = '[data-native-chat-transcript-column]'
const TRANSCRIPT_SCROLL_SELECTOR = '[data-native-chat-scroll]'
/** Row chrome that only shows on hover (timestamps, copy): marked, so counts never follow the mouse. */
/** Row chrome, not transcript (timestamps, copy): marked, so find counts only message text. */
const SKIPPED_TEXT_SELECTOR = '.sr-only, [hidden], [data-native-chat-find-skip]'
/** A streamed reply's text node while its words fade in: each new word is its own element. */
const WORD_GROUP_SELECTOR = '[data-word-group]'