refactor: address code review feedback for error display

- Extract shared CollapsibleErrorSection component to eliminate code duplication
- Use Tailwind CSS classes instead of inline styles for consistency
- Add proper accessibility attributes (role, tabIndex, aria-expanded, keyboard handlers)
- Auto-detect language for error content (xml for error tags, text otherwise)
- Maintain feature parity between error and diff_error displays
This commit is contained in:
Hannes Rudolph 2025-08-25 17:20:03 -06:00
parent 446bafb812
commit 44e4feecb8
2 changed files with 91 additions and 115 deletions

View file

@ -2,7 +2,7 @@ import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "
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, FollowUpData, SuggestionItem } from "@roo-code/types"
import { Mode } from "@roo/modes"
@ -11,7 +11,6 @@ import { ClineApiReqInfo, ClineAskUseMcpServer, ClineSayTool } from "@roo/Extens
import { COMMAND_OUTPUT_STRING } from "@roo/combineCommandSequences"
import { safeJsonParse } from "@roo/safeJsonParse"
import { useCopyToClipboard } from "@src/utils/clipboard"
import { useExtensionState } from "@src/context/ExtensionStateContext"
import { findMatchingResourceOrTemplate } from "@src/utils/mcp"
import { vscode } from "@src/utils/vscode"
@ -22,7 +21,6 @@ import { Button } from "@src/components/ui"
import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock"
import UpdateTodoListToolBlock from "./UpdateTodoListToolBlock"
import CodeAccordian from "../common/CodeAccordian"
import CodeBlock from "../common/CodeBlock"
import MarkdownBlock from "../common/MarkdownBlock"
import { ReasoningBlock } from "./ReasoningBlock"
import Thumbnails from "../common/Thumbnails"
@ -47,6 +45,7 @@ import { McpExecution } from "./McpExecution"
import { ChatTextArea } from "./ChatTextArea"
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
import { useSelectedModel } from "../ui/hooks/useSelectedModel"
import { CollapsibleErrorSection } from "./CollapsibleErrorSection"
interface ChatRowProps {
message: ClineMessage
@ -120,14 +119,11 @@ export const ChatRowContent = ({
const { info: model } = useSelectedModel(apiConfiguration)
const [reasoningCollapsed, setReasoningCollapsed] = useState(true)
const [isDiffErrorExpanded, setIsDiffErrorExpanded] = useState(false)
const [showCopySuccess, setShowCopySuccess] = useState(false)
const [isEditing, setIsEditing] = useState(false)
const [editedContent, setEditedContent] = useState("")
const [editMode, setEditMode] = useState<Mode>(mode || "code")
const [editImages, setEditImages] = useState<string[]>([])
const [isErrorExpanded, setIsErrorExpanded] = useState(false) // Default collapsed like diff_error
const [showErrorCopySuccess, setShowErrorCopySuccess] = useState(false)
const { copyWithFeedback } = useCopyToClipboard()
// Handle message events for image selection during edit mode
useEffect(() => {
@ -961,63 +957,13 @@ export const ChatRowContent = ({
switch (message.say) {
case "diff_error":
return (
<div>
<div className="mt-0 overflow-hidden mb-2">
<div
className={`${
isDiffErrorExpanded ? "border-b border-vscode-editorGroup-border" : ""
} font-normal text-base text-vscode-editor-foreground flex items-center justify-between cursor-pointer focus:outline focus:outline-2 focus:outline-vscode-focusBorder`}
role="button"
tabIndex={0}
aria-expanded={isDiffErrorExpanded}
aria-label={t("chat:diffError.title")}
onClick={() => setIsDiffErrorExpanded(!isDiffErrorExpanded)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault()
setIsDiffErrorExpanded(!isDiffErrorExpanded)
}
}}>
<div className="flex items-center gap-2.5 flex-grow">
<span
className="codicon codicon-warning text-vscode-editorWarning-foreground opacity-80"
style={{ fontSize: 16, marginBottom: "-1.5px" }}></span>
<span className="font-bold">{t("chat:diffError.title")}</span>
</div>
<div className="flex items-center">
<VSCodeButton
appearance="icon"
className="p-[3px] h-6 mr-1 text-vscode-editor-foreground flex items-center justify-center bg-transparent"
onClick={(e) => {
e.stopPropagation()
// Call copyWithFeedback and handle the Promise
copyWithFeedback(message.text || "").then((success) => {
if (success) {
// Show checkmark
setShowCopySuccess(true)
// Reset after a brief delay
setTimeout(() => {
setShowCopySuccess(false)
}, 1000)
}
})
}}>
<span
className={`codicon codicon-${showCopySuccess ? "check" : "copy"}`}></span>
</VSCodeButton>
<span
className={`codicon codicon-chevron-${isDiffErrorExpanded ? "up" : "down"}`}></span>
</div>
</div>
{isDiffErrorExpanded && (
<div className="p-2 bg-vscode-editor-background">
<CodeBlock source={message.text || ""} language="xml" />
</div>
)}
</div>
</div>
<CollapsibleErrorSection
title={t("chat:diffError.title")}
content={message.text}
language="xml"
isExpanded={isDiffErrorExpanded}
onToggleExpand={() => setIsDiffErrorExpanded(!isDiffErrorExpanded)}
/>
)
case "subtask_result":
return (
@ -1216,59 +1162,16 @@ export const ChatRowContent = ({
</div>
)
case "error":
// Detect language based on content - check if it contains XML-like error tags
const errorLanguage = message.text?.includes("<error>") ? "xml" : "text"
return (
<div>
<div className="mt-0 overflow-hidden mb-2">
<div
className={`${
isErrorExpanded ? "border-b border-vscode-editorGroup-border" : ""
} font-normal text-base text-vscode-editor-foreground flex items-center justify-between cursor-pointer focus:outline focus:outline-2 focus:outline-vscode-focusBorder`}
role="button"
tabIndex={0}
aria-expanded={isErrorExpanded}
aria-label={message.title || t("chat:error")}
onClick={() => setIsErrorExpanded(!isErrorExpanded)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault()
setIsErrorExpanded(!isErrorExpanded)
}
}}>
<div className="flex items-center gap-2.5 flex-grow">
<span
className="codicon codicon-warning text-vscode-editorWarning-foreground opacity-80"
style={{ fontSize: 16, marginBottom: "-1.5px" }}></span>
<span className="font-bold">{message.title || t("chat:error")}</span>
</div>
<div className="flex items-center">
<VSCodeButton
appearance="icon"
className="p-[3px] h-6 mr-1 text-vscode-editor-foreground flex items-center justify-center bg-transparent"
onClick={(e) => {
e.stopPropagation()
copyWithFeedback(message.text || "").then((success) => {
if (success) {
setShowErrorCopySuccess(true)
setTimeout(() => {
setShowErrorCopySuccess(false)
}, 1000)
}
})
}}>
<span
className={`codicon codicon-${showErrorCopySuccess ? "check" : "copy"}`}></span>
</VSCodeButton>
<span
className={`codicon codicon-chevron-${isErrorExpanded ? "up" : "down"}`}></span>
</div>
</div>
{isErrorExpanded && (
<div className="p-2 bg-vscode-editor-background">
<CodeBlock source={message.text || ""} language="xml" />
</div>
)}
</div>
</div>
<CollapsibleErrorSection
title={message.title || t("chat:error")}
content={message.text}
language={errorLanguage}
isExpanded={isErrorExpanded}
onToggleExpand={() => setIsErrorExpanded(!isErrorExpanded)}
/>
)
case "completion_result":
return (

View file

@ -0,0 +1,73 @@
import React from "react"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import CodeBlock from "../common/CodeBlock"
import { useCopyToClipboard } from "@src/utils/clipboard"
interface CollapsibleErrorSectionProps {
title: string
content: string | null | undefined
language?: string
isExpanded: boolean
onToggleExpand: () => void
}
export const CollapsibleErrorSection: React.FC<CollapsibleErrorSectionProps> = ({
title,
content,
language = "xml",
isExpanded,
onToggleExpand,
}) => {
const [showCopySuccess, setShowCopySuccess] = React.useState(false)
const { copyWithFeedback } = useCopyToClipboard()
const handleCopy = async (e: React.MouseEvent) => {
e.stopPropagation()
const success = await copyWithFeedback(content || "")
if (success) {
setShowCopySuccess(true)
setTimeout(() => setShowCopySuccess(false), 1000)
}
}
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault()
onToggleExpand()
}
}
return (
<div className="mt-0 overflow-hidden mb-2">
<div
className={`${
isExpanded ? "border-b border-vscode-editorGroup-border" : ""
} font-normal text-base text-vscode-editor-foreground flex items-center justify-between cursor-pointer focus:outline focus:outline-2 focus:outline-vscode-focusBorder`}
role="button"
tabIndex={0}
aria-expanded={isExpanded}
aria-label={title}
onClick={onToggleExpand}
onKeyDown={handleKeyDown}>
<div className="flex items-center gap-2.5 flex-grow">
<span className="codicon codicon-warning text-vscode-editorWarning-foreground opacity-80 text-base -mb-0.5"></span>
<span className="font-bold">{title}</span>
</div>
<div className="flex items-center">
<VSCodeButton
appearance="icon"
className="p-[3px] h-6 mr-1 text-vscode-editor-foreground flex items-center justify-center bg-transparent"
onClick={handleCopy}>
<span className={`codicon codicon-${showCopySuccess ? "check" : "copy"}`}></span>
</VSCodeButton>
<span className={`codicon codicon-chevron-${isExpanded ? "up" : "down"}`}></span>
</div>
</div>
{isExpanded && (
<div className="p-2 bg-vscode-editor-background">
<CodeBlock source={content || ""} language={language} />
</div>
)}
</div>
)
}