fix(web-search-card): finish search bot-review round (import, docs)

Complete the stalled review pass: drop the unused useMemo import (rows flatten
inline), add SearchBlock to the ui-primitives README (both languages) with a
Search results section, and re-record the doc pairings. Search card behavior
and tests unchanged (313 pass).
This commit is contained in:
Chinesezjc
2026-07-30 20:42:25 +08:00
parent 3dbf83e918
commit b832f46eff
16 changed files with 117 additions and 59 deletions
@@ -20,7 +20,7 @@ import { InputBar } from './skeleton/InputBar.tsx'
import { ChatView } from './chat/ChatView.tsx'
import { StatsLine } from './chat/StatsLine.tsx'
import { bashToolviewSample } from './toolviews/bash-sample.tsx'
import { searchToolview } from './toolviews/search-sample.tsx'
import { searchToolview } from './toolviews/search-row.tsx'
import { ApprovalPanel } from './skeleton/ApprovalPanel.tsx'
import { todoToolview } from './toolviews/todo-row.tsx'
import { askQuestionToolview } from './toolviews/ask-question-row.tsx'
@@ -65,8 +65,10 @@ export interface SearchCardModel {
* a still-running call (no result view) is null, as is a settled call whose
* result view is not a search card — including a `card` value this UI version
* does not know, which arrives over the wire and cannot be trusted to be one of
* the compiled variants, and a generic result a `grep`/`glob` failure or nested
* `run_code` dispatch produces (its text keeps the generic path).
* the compiled variants, a `card: 'search'` view whose `kind` is neither
* `matches` nor `paths` (equally untrusted wire data), and a generic result a
* `grep`/`glob` failure or nested `run_code` dispatch produces (its text keeps
* the generic path).
* @param block - RunningToolCall or ToolResultNode off the snapshot caches.
* @returns the search-card props, or null for the generic path.
*/
@@ -76,10 +78,15 @@ export function searchCardModel(block: ToolCallBlock): SearchCardModel | null {
const result = block.resultView?.card === 'search' ? block.resultView : null
if (result === null) return null
const common = { truncated: result.truncated, total: result.total }
return {
title: result.title,
card: result.kind === 'matches'
? { kind: 'matches', files: result.files, ...common }
: { kind: 'paths', paths: result.paths, ...common },
if (result.kind === 'matches') {
return { title: result.title, card: { kind: 'matches', files: result.files, ...common } }
}
// `kind` rides the same untrusted wire frame as `card`, so a version mismatch
// or a loose protocol producer could deliver a `card: 'search'` subtype this
// client does not compile. Guard the paths shape explicitly: an unknown kind
// falls to the generic path rather than being rendered as a paths card, which
// would leave SearchBlock calling `.length`/`.map` on an absent `paths`.
// oxlint-disable-next-line typescript/no-unnecessary-condition -- kind is wire data; the compiled union cannot prove this exhaustive.
if (result.kind !== 'paths') return null
return { title: result.title, card: { kind: 'paths', paths: result.paths, ...common } }
}
@@ -101,8 +101,9 @@
font: var(--dsw-font-xs-13);
}
/* The terminal card sits directly under its section label, so it drops the
primitive's standalone vertical margin; the section owns the spacing. */
/* A render-intent card (terminal or search) sits directly under its section
label, so it drops the primitive's standalone vertical margin; the section
owns the spacing. */
.terminal {
margin: 0;
}
@@ -17,7 +17,7 @@ import { IconSearchOutline16, SearchBlock, StateDot } from '@deepseek-ai/dsh-cli
import type { ToolRowProps } from '../contract/slots.ts'
import { CHAT_SEARCH_MAX_LINES, searchCardModel } from '../contract/search-card-model.ts'
import { toolRowModel, type ToolRowState } from '../contract/tool-call-model.ts'
import css from './search-sample.module.css'
import css from './search-row.module.css'
/** Leading-slot glyph substitution: the search icon yields to the terminal
* state semantic (error = red, interrupted = amber). Running keeps the icon