From 6608ede1a033735ea95409995bf0d5bc523f19f8 Mon Sep 17 00:00:00 2001 From: Chinesezjc Date: Thu, 30 Jul 2026 20:49:23 +0800 Subject: [PATCH] fix(web-search-card): surface result text when an errored search has no card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit grep/glob return no presentResult on an error result, so an errored search had no card and the keyed SearchRow showed only a red dot — the model-facing error text (bad pattern, missing path, a nested run_code dispatch with no card) was nowhere on screen. Add an error-text arm mirroring the file-mutation and read rows. Added tests for the text arm and its name/code fallback. The unknown-kind fallback in search-card-model is already guarded (returns null → generic path). --- .../client/toolviews/search-row.module.css | 11 ++++++++ .../src/client/toolviews/search-row.tsx | 25 +++++++++++++++++++ .../tests/search-card.spec.tsx | 19 ++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/packages/client/ui-conversation/src/client/toolviews/search-row.module.css b/packages/client/ui-conversation/src/client/toolviews/search-row.module.css index 5c4eb1f7db..dd0395ec1d 100644 --- a/packages/client/ui-conversation/src/client/toolviews/search-row.module.css +++ b/packages/client/ui-conversation/src/client/toolviews/search-row.module.css @@ -93,3 +93,14 @@ clip: rect(0 0 0 0); white-space: nowrap; } + +/* The result text for an errored search, indented to the card's own column and + in the error tone, standing in for the search card the failure path does not + produce. */ +.failure { + margin: 4px 0 4px 22px; + white-space: pre-wrap; + overflow-wrap: anywhere; + font: var(--dsw-font-xs-13); + color: var(--dsw-alias-state-error-primary); +} diff --git a/packages/client/ui-conversation/src/client/toolviews/search-row.tsx b/packages/client/ui-conversation/src/client/toolviews/search-row.tsx index 90ec5e3470..0726f30a3c 100644 --- a/packages/client/ui-conversation/src/client/toolviews/search-row.tsx +++ b/packages/client/ui-conversation/src/client/toolviews/search-row.tsx @@ -40,6 +40,27 @@ function stateStatus(state: ToolRowState): string | null { } } +/** + * A settled result's text, flattened from its content blocks, for the arm that + * shows a failure the search card cannot: grep/glob have no `presentResult` on + * an error result, so an errored search has no card, and the keyed row is not a + * details-panel target. Without this the failure — a bad pattern, a missing + * path, a nested run_code dispatch that returned no card — would read as a bare + * red dot with the model-facing error text nowhere on screen. + * @param block - the frozen call slice. + * @returns the result text, or null for a running call or an empty result. + */ +function errorText(block: ToolRowProps['block']): string | null { + if (!('kind' in block)) return null + const parts: string[] = [] + for (const item of block.content) { + if (item.type === 'text') parts.push(item.text) + } + if (parts.length === 0 && block.error !== undefined) parts.push(`${block.error.name}: ${block.error.code}`) + const text = parts.join('\n') + return text === '' ? null : text +} + /** * Search row: icon + Search · {summary} in the shared ToolRow chrome, with the * completed search's card resident below it. The summary row is not a @@ -51,6 +72,9 @@ export function SearchRow({ toolName, block }: ToolRowProps) { const model = toolRowModel(toolName, block) const search = searchCardModel(block) const status = stateStatus(model.state) + // An errored search has no card (grep/glob return no presentResult on error); + // surface its result text so the failure is more than a red dot. + const failure = search === null && model.state === 'error' ? errorText(block) : null return (
@@ -65,6 +89,7 @@ export function SearchRow({ toolName, block }: ToolRowProps) { {search !== null && ( )} + {failure !== null &&
{failure}
}
) } diff --git a/packages/client/ui-conversation/tests/search-card.spec.tsx b/packages/client/ui-conversation/tests/search-card.spec.tsx index c2ff38755c..6d566b1b01 100644 --- a/packages/client/ui-conversation/tests/search-card.spec.tsx +++ b/packages/client/ui-conversation/tests/search-card.spec.tsx @@ -184,6 +184,25 @@ describe('SearchRow keyed card', () => { expect(errorView.container.querySelector('[data-variant="search"]')?.getAttribute('data-state')).toBe('error') }) + it('surfaces the result text when an errored search has no card', () => { + // grep/glob return no presentResult on error → no card; the row shows the + // model-facing error text instead of a bare red dot. + const view = render() + expect(searchKindOf(view.container)).toBeNull() + expect(view.getByText('grep: invalid regular expression')).toBeTruthy() + }) + + it('falls back to the error name/code when an errored result has no text block', () => { + const view = render() + expect(view.getByText('ToolError: timeout')).toBeTruthy() + }) + it('shows the result view\'s replacement title instead of the args summary', () => { const view = render(