Skip to content
116 changes: 85 additions & 31 deletions webview-ui/src/components/chat/ChatRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { McpExecution } from "./McpExecution"
import { useSize } from "react-use"
import { useTranslation, Trans } from "react-i18next"
import deepEqual from "fast-deep-equal"
import { VSCodeBadge, VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { VSCodeBadge } from "@vscode/webview-ui-toolkit/react"

import type { ClineMessage } from "@roo-code/types"
import { Mode } from "@roo/modes"
Expand Down Expand Up @@ -33,6 +33,7 @@ import MarkdownBlock from "../common/MarkdownBlock"
import { ReasoningBlock } from "./ReasoningBlock"
import Thumbnails from "../common/Thumbnails"
import McpResourceRow from "../mcp/McpResourceRow"
import { IconButton } from "./IconButton"

import { Mention } from "./Mention"
import { CheckpointSaved } from "./checkpoints/CheckpointSaved"
Expand Down Expand Up @@ -118,6 +119,8 @@ export const ChatRowContent = ({
const [reasoningCollapsed, setReasoningCollapsed] = useState(true)
const [isDiffErrorExpanded, setIsDiffErrorExpanded] = useState(false)
const [showCopySuccess, setShowCopySuccess] = useState(false)
const [isErrorExpanded, setIsErrorExpanded] = useState(false)
const [showErrorCopySuccess, setShowErrorCopySuccess] = useState(false)
const [isEditing, setIsEditing] = useState(false)
const [editedContent, setEditedContent] = useState("")
const [editMode, setEditMode] = useState<Mode>(mode || "code")
Expand Down Expand Up @@ -893,37 +896,22 @@ export const ChatRowContent = ({
<span style={{ fontWeight: "bold" }}>{t("chat:diffError.title")}</span>
</div>
<div style={{ display: "flex", alignItems: "center" }}>
<VSCodeButton
appearance="icon"
style={{
padding: "3px",
height: "24px",
marginRight: "4px",
color: "var(--vscode-editor-foreground)",
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "transparent",
}}
onClick={(e) => {
<IconButton
Copy link

Copilot AI Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copy feedback logic for the diff error and the new error display is duplicated. Consider extracting this into a reusable function to reduce code duplication.

Copilot uses AI. Check for mistakes.
iconClass={showCopySuccess ? "codicon-check" : "codicon-copy"}
title={t("chat:codeblock.tooltips.copy_code")}
onClick={(e: React.MouseEvent) => {
e.stopPropagation()

// Call copyWithFeedback and handle the Promise
copyWithFeedback(message.text || "").then((success) => {
if (success) {
// Show checkmark
setShowCopySuccess(true)
Copy link

Copilot AI Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copy feedback state being set here should be setShowErrorCopySuccess(true) instead of setShowCopySuccess(true) to match the new error display state variables.

Copilot uses AI. Check for mistakes.

// Reset after a brief delay
setTimeout(() => {
setShowCopySuccess(false)
Copy link

Copilot AI Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copy feedback state being set here should be setShowErrorCopySuccess(false) instead of setShowCopySuccess(false) to match the new error display state variables.

Suggested change
setShowCopySuccess(false)
setShowErrorCopySuccess(false)

Copilot uses AI. Check for mistakes.
}, 1000)
}
})
}}>
<span
className={`codicon codicon-${showCopySuccess ? "check" : "copy"}`}></span>
</VSCodeButton>
}}
style={{ marginRight: "4px" }}
/>
<span
className={`codicon codicon-chevron-${isDiffErrorExpanded ? "up" : "down"}`}></span>
</div>
Expand Down Expand Up @@ -1132,15 +1120,81 @@ export const ChatRowContent = ({
)
case "error":
return (
<>
{title && (
<div style={headerStyle}>
{icon}
{title}
<div>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice there's significant code duplication between the error and diff_error implementations. Both share nearly identical structure for the collapsible UI, copy functionality, and expand/collapse behavior. Could we consider extracting a reusable component like to reduce this duplication and make future maintenance easier?

<div
style={{
marginTop: "0px",
overflow: "hidden",
marginBottom: "8px",
}}>
<div
style={{
borderBottom: isErrorExpanded
? "1px solid var(--vscode-editorGroup-border)"
: "none",
fontWeight: "normal",
fontSize: "var(--vscode-font-size)",
color: "var(--vscode-editor-foreground)",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
cursor: "pointer",
}}
onClick={() => setIsErrorExpanded(!isErrorExpanded)}>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clickable div lacks accessibility attributes. Consider adding:

This would improve keyboard navigation and screen reader support.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding accessibility attributes (e.g. role="button" and tabIndex) to the clickable error header div to improve keyboard navigation and screen reader support.

Suggested change
onClick={() => setIsErrorExpanded(!isErrorExpanded)}>
onClick={() => setIsErrorExpanded(!isErrorExpanded)} role="button" tabIndex={0}>

<div
style={{
display: "flex",
alignItems: "center",
gap: "10px",
flexGrow: 1,
}}>
<span
className="codicon codicon-error"
style={{
color: "var(--vscode-errorForeground)",
opacity: 0.8,
fontSize: 16,
marginBottom: "-1.5px",
}}></span>
<span style={{ fontWeight: "bold", color: "var(--vscode-errorForeground)" }}>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intentional that the "Error" label uses color while the diff_error "Diff Error" label uses the default color? This creates a visual inconsistency between the two similar components. If this is meant to indicate severity (errors being more critical than diff errors), that's fine, but wanted to confirm this was deliberate.

{t("chat:error")}
</span>
</div>
<div style={{ display: "flex", alignItems: "center" }}>
<IconButton
iconClass={showErrorCopySuccess ? "codicon-check" : "codicon-copy"}
title={t("chat:codeblock.tooltips.copy_code")}
onClick={(e: React.MouseEvent) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copy button’s onClick handler in the error block duplicates logic found in the diff_error block. Consider abstracting this copy-with-feedback functionality into a reusable component or helper to reduce duplication.

e.stopPropagation()
copyWithFeedback(message.text || "").then((success) => {
if (success) {
setShowErrorCopySuccess(true)
setTimeout(() => {
setShowErrorCopySuccess(false)
}, 1000)
}
})
}}
style={{ marginRight: "4px" }}
/>
<span
className={`codicon codicon-chevron-${isErrorExpanded ? "up" : "down"}`}></span>
</div>
</div>
)}
<p style={{ ...pStyle, color: "var(--vscode-errorForeground)" }}>{message.text}</p>
</>
{isErrorExpanded && (
<div
style={{
padding: "8px",
backgroundColor: "var(--vscode-editor-background)",
borderTop: "none",
}}>
<p style={{ ...pStyle, color: "var(--vscode-errorForeground)" }}>
{message.text}
</p>
</div>
)}
</div>
</div>
)
case "completion_result":
return (
Expand Down
Loading