From 0168da7bcd5c9822dae250e672f715f5b2bf5cbd Mon Sep 17 00:00:00 2001 From: Simos Mikelatos Date: Mon, 29 Jun 2026 07:04:04 +0000 Subject: [PATCH] Remove duplicate command grouping from PR 929 --- src/components/chat/tools/ToolRenderer.tsx | 12 +- .../tools/components/BashCommandDisplay.tsx | 1 + .../chat/tools/components/CommandRunGroup.tsx | 124 ------------------ src/components/chat/tools/components/index.ts | 1 - .../view/subcomponents/ChatMessagesPane.tsx | 97 +++----------- 5 files changed, 31 insertions(+), 204 deletions(-) delete mode 100644 src/components/chat/tools/components/CommandRunGroup.tsx diff --git a/src/components/chat/tools/ToolRenderer.tsx b/src/components/chat/tools/ToolRenderer.tsx index a02e5aa1..0d9e1f6a 100644 --- a/src/components/chat/tools/ToolRenderer.tsx +++ b/src/components/chat/tools/ToolRenderer.tsx @@ -129,8 +129,16 @@ export const ToolRenderer: React.FC = memo(({ // a chevron that expands to show the output inline. The combined view lives on // the input render; the separate result section is suppressed in MessageComponent. if (toolName === 'Bash' && mode === 'input') { - const command = parsedData?.command || ''; - const description = parsedData?.description; + const command = typeof parsedData === 'object' && parsedData !== null && 'command' in parsedData + ? String(parsedData.command || '') + : typeof toolInput === 'string' + ? toolInput + : typeof rawToolInput === 'string' + ? rawToolInput + : ''; + const description = typeof parsedData === 'object' && parsedData !== null && 'description' in parsedData + ? String(parsedData.description || '') + : undefined; const output = typeof toolResult?.content === 'string' ? toolResult.content : toolResult?.content != null diff --git a/src/components/chat/tools/components/BashCommandDisplay.tsx b/src/components/chat/tools/components/BashCommandDisplay.tsx index 8b9fc744..bddce924 100644 --- a/src/components/chat/tools/components/BashCommandDisplay.tsx +++ b/src/components/chat/tools/components/BashCommandDisplay.tsx @@ -120,6 +120,7 @@ export const BashCommandDisplay: React.FC = ({ - - {open && ( -
- {commands.map((cmd) => ( - - ))} -
- )} - - ); -}; diff --git a/src/components/chat/tools/components/index.ts b/src/components/chat/tools/components/index.ts index f0b6249e..225526cc 100644 --- a/src/components/chat/tools/components/index.ts +++ b/src/components/chat/tools/components/index.ts @@ -2,7 +2,6 @@ export { CollapsibleSection } from './CollapsibleSection'; export { ToolDiffViewer } from './ToolDiffViewer'; export { OneLineDisplay } from './OneLineDisplay'; export { BashCommandDisplay } from './BashCommandDisplay'; -export { CommandRunGroup } from './CommandRunGroup'; export { CollapsibleDisplay } from './CollapsibleDisplay'; export { SubagentContainer } from './SubagentContainer'; export * from './ContentRenderers'; diff --git a/src/components/chat/view/subcomponents/ChatMessagesPane.tsx b/src/components/chat/view/subcomponents/ChatMessagesPane.tsx index b02fb89e..5573b31f 100644 --- a/src/components/chat/view/subcomponents/ChatMessagesPane.tsx +++ b/src/components/chat/view/subcomponents/ChatMessagesPane.tsx @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next'; import { useCallback, useRef } from 'react'; -import type { Dispatch, ReactNode, RefObject, SetStateAction } from 'react'; +import type { Dispatch, RefObject, SetStateAction } from 'react'; import type { ChatMessage } from '../../types/types'; import type { @@ -13,7 +13,6 @@ import { getIntrinsicMessageKey } from '../../utils/messageKeys'; import MessageComponent from './MessageComponent'; import ProviderSelectionEmptyState from './ProviderSelectionEmptyState'; -import { CommandRunGroup } from '../../tools'; interface ChatMessagesPaneProps { scrollContainerRef: RefObject; @@ -253,81 +252,25 @@ export default function ChatMessagesPane({ )} - {(() => { - const isBashCommand = (m: ChatMessage | null | undefined) => - Boolean(m && m.isToolUse && m.toolName === 'Bash' && !m.isSubagentContainer); - // Messages that render nothing (e.g. thinking hidden when showThinking - // is off) shouldn't break a visual run of commands. - const isRendered = (m: ChatMessage) => !(m.isThinking && !showThinking); - - const items: ReactNode[] = []; - - for (let index = 0; index < visibleMessages.length; index++) { - const message = visibleMessages[index]; - - // Collapse a run of 2+ consecutive shell commands under a single - // header so long command runs stay tidy (Codex-in-VSCode style). - // Skip over non-rendered messages (e.g. hidden reasoning that Codex - // interleaves between commands) so they don't split the run. - if (isBashCommand(message)) { - const runIndices = [index]; - let cursor = index + 1; - while (cursor < visibleMessages.length) { - const candidate = visibleMessages[cursor]; - if (!isRendered(candidate)) { - cursor++; - continue; - } - if (isBashCommand(candidate)) { - runIndices.push(cursor); - cursor++; - continue; - } - break; - } - if (runIndices.length >= 2) { - const groupMessages = runIndices.map((i) => visibleMessages[i]); - items.push( - , - ); - // Consume everything up to the last command in the run (any - // trailing skipped messages render nothing anyway). - index = runIndices[runIndices.length - 1]; - continue; - } - } - - // Walk back past messages that are not actually rendered (e.g. thinking - // messages hidden when showThinking is off). Otherwise a hidden thinking - // message would make the following message look "grouped" and suppress its - // provider header/icon — which is why Claude turns lost their icon. - let prevMessage: ChatMessage | null = null; - for (let i = index - 1; i >= 0; i--) { - const candidate = visibleMessages[i]; - if (candidate.isThinking && !showThinking) continue; - prevMessage = candidate; - break; - } - items.push( - , - ); - } - - return items; - })()} + {visibleMessages.map((message, index) => { + const prevMessage = index > 0 ? visibleMessages[index - 1] : null; + return ( + + ); + })} )}