From 702b43901aa57546cf9706dcd603d800e653af6e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Aug 2026 13:31:06 +0000 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=A7=A9=20refactor:=20Resolve=20Tool-C?= =?UTF-8?q?ard=20State=20Once=20(AI-1810)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each tool card derived its state several times over — the visible label from one expression, the `aria-live` announcement from another, the icon and shimmer from a third, and since #14906 the follow-scroll from a fourth. Nothing tied them together; they agreed only because each was written to agree. Thirteen of the seventeen review findings on #14873 were instances of one derivation being updated and another left behind, and #14892 added more. `resolveToolCallPhase` is now the single source: one function encoding the precedence rules, each of which a specific review finding established, returning `running | completed | cancelled | failed`. Everything the card shows reads that value. `ProgressText` takes `phase` in place of the `error` + `errorSuffix` pair, which encoded three terminal states in two booleans — `error` meant cancelled, a present `errorSuffix` meant failed — and made every consumer reconstruct the distinction. That shape is precisely what let a duration render beside "failed" (Codex round 1 on #14892). Two things fell out once the state had one home, both dead code rather than deletions of behaviour: - `progress` left `ProgressText` entirely; the phase already carries everything it was used to decide. - The `useProgress` mask went with it. Passing 1 in still matters — it stops the 200ms interval — but masking the output no longer does, because the phase treats an explicit close as terminal outright. The "both halves are load-bearing" subtlety is now one half. Scope: the nine cards that render the shared `ProgressText`. The three with bespoke layouts (`WebSearch`, `SubagentCall`, `OpenAIImageGen`) still resolve their own state and are the natural follow-up — they can adopt the resolver without adopting the component. Refactor-only. 4891/4891 client tests pass unchanged, including the suites that encode the cancelled/failed precedence in both directions. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014vLhxCFMYkCaTsoFTiAjJ5 --- .../Chat/Messages/Content/CodeAnalyze.tsx | 18 +++- .../Chat/Messages/Content/Parts/BashCall.tsx | 44 +++++---- .../Messages/Content/Parts/ExecuteCode.tsx | 44 +++++---- .../Content/Parts/FileAuthoringCall.tsx | 29 +++--- .../Messages/Content/Parts/ReadFileCall.tsx | 18 ++-- .../Chat/Messages/Content/Parts/SkillCall.tsx | 18 ++-- .../Content/Parts/__tests__/BashCall.test.tsx | 12 +-- .../__tests__/FileAuthoringCall.test.tsx | 14 +-- .../Content/Parts/useToolCallState.ts | 79 +++++++++------- .../Chat/Messages/Content/ProgressText.tsx | 56 +++++------- .../Chat/Messages/Content/RetrievalCall.tsx | 44 +++++---- .../Chat/Messages/Content/ToolCall.tsx | 41 +++++---- .../Content/__tests__/ProgressText.test.tsx | 20 ++--- .../src/utils/__tests__/toolCallPhase.spec.ts | 90 +++++++++++++++++++ client/src/utils/index.ts | 1 + client/src/utils/toolCallPhase.ts | 82 +++++++++++++++++ 16 files changed, 410 insertions(+), 200 deletions(-) create mode 100644 client/src/utils/__tests__/toolCallPhase.spec.ts create mode 100644 client/src/utils/toolCallPhase.ts diff --git a/client/src/components/Chat/Messages/Content/CodeAnalyze.tsx b/client/src/components/Chat/Messages/Content/CodeAnalyze.tsx index dd80e95d783..26176ba4106 100644 --- a/client/src/components/Chat/Messages/Content/CodeAnalyze.tsx +++ b/client/src/components/Chat/Messages/Content/CodeAnalyze.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react'; import { useRecoilValue } from 'recoil'; import { Terminal } from 'lucide-react'; +import type { ToolCallPhase } from '~/utils/toolCallPhase'; import { useProgress, useLocalize } from '~/hooks'; import ProgressText from './ProgressText'; import MarkdownLite from './MarkdownLite'; @@ -46,14 +47,22 @@ export default function CodeAnalyze({ return acc; }, ''); + /** + * The legacy assistants-endpoint card: it never receives run-step metadata, + * so it genuinely has only these two states and maps them directly rather + * than through `resolveToolCallPhase`, which needs signals this card has no + * access to. The announcement and the icon below read this same value. + */ + const phase: ToolCallPhase = progress < 1 ? 'running' : 'completed'; + return ( <> - {progress < 1 ? localize('com_ui_analyzing') : localize('com_ui_analyzing_finished')} + {phase === 'running' ? localize('com_ui_analyzing') : localize('com_ui_analyzing_finished')}