From fe1620cdf569d52fac978cd264cd1b081bda95b6 Mon Sep 17 00:00:00 2001
From: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
Date: Mon, 9 Nov 2026 20:39:58 +0000
Subject: [PATCH] fix(chat): always show copy and fork under a finished agent
turn (#40474)
---
.../messages/actions/NativeChatAgentControls.tsx | 2 +-
.../messages/actions/use-native-chat-reveal-latest.ts | 2 +-
.../messages/rows/NativeChatAssistantMessageRow.tsx | 9 ++-------
.../native-chat/messages/rows/NativeChatMessageRow.tsx | 2 +-
.../pane/actions/NativeChatContextMenuItems.tsx | 2 +-
.../viewport/find/use-native-chat-find-matches.ts | 2 +-
6 files changed, 7 insertions(+), 12 deletions(-)
diff --git a/src/renderer/src/components/native-chat/messages/actions/NativeChatAgentControls.tsx b/src/renderer/src/components/native-chat/messages/actions/NativeChatAgentControls.tsx
index 34e6b81cbf3..5db891afcaf 100644
--- a/src/renderer/src/components/native-chat/messages/actions/NativeChatAgentControls.tsx
+++ b/src/renderer/src/components/native-chat/messages/actions/NativeChatAgentControls.tsx
@@ -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.
{/* 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. */}
diff --git a/src/renderer/src/components/native-chat/messages/actions/use-native-chat-reveal-latest.ts b/src/renderer/src/components/native-chat/messages/actions/use-native-chat-reveal-latest.ts
index 00c476f9091..19b8e0e927f 100644
--- a/src/renderer/src/components/native-chat/messages/actions/use-native-chat-reveal-latest.ts
+++ b/src/renderer/src/components/native-chat/messages/actions/use-native-chat-reveal-latest.ts
@@ -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
diff --git a/src/renderer/src/components/native-chat/messages/rows/NativeChatAssistantMessageRow.tsx b/src/renderer/src/components/native-chat/messages/rows/NativeChatAssistantMessageRow.tsx
index 8f7fc2045ae..9af89d8fddb 100644
--- a/src/renderer/src/components/native-chat/messages/rows/NativeChatAssistantMessageRow.tsx
+++ b/src/renderer/src/components/native-chat/messages/rows/NativeChatAssistantMessageRow.tsx
@@ -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 (
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'
)}
diff --git a/src/renderer/src/components/native-chat/messages/rows/NativeChatMessageRow.tsx b/src/renderer/src/components/native-chat/messages/rows/NativeChatMessageRow.tsx
index fbf666c74ab..11c9bbda907 100644
--- a/src/renderer/src/components/native-chat/messages/rows/NativeChatMessageRow.tsx
+++ b/src/renderer/src/components/native-chat/messages/rows/NativeChatMessageRow.tsx
@@ -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
diff --git a/src/renderer/src/components/native-chat/pane/actions/NativeChatContextMenuItems.tsx b/src/renderer/src/components/native-chat/pane/actions/NativeChatContextMenuItems.tsx
index bd19901886f..c39e388ffc4 100644
--- a/src/renderer/src/components/native-chat/pane/actions/NativeChatContextMenuItems.tsx
+++ b/src/renderer/src/components/native-chat/pane/actions/NativeChatContextMenuItems.tsx
@@ -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(() => {})
}
diff --git a/src/renderer/src/components/native-chat/viewport/find/use-native-chat-find-matches.ts b/src/renderer/src/components/native-chat/viewport/find/use-native-chat-find-matches.ts
index b0dcadc3168..2801dd93655 100644
--- a/src/renderer/src/components/native-chat/viewport/find/use-native-chat-find-matches.ts
+++ b/src/renderer/src/components/native-chat/viewport/find/use-native-chat-find-matches.ts
@@ -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]'