fix(web-search-card): surface truncation recovery, widen cardless fallback, validate wire shape, fix tail-cap
Address the ds-review-bot findings on the search card:
- searchCardModel dropped the result view's `content`, so a capped search's
`Full … stored at: <locator>` recovery footer vanished from the UI (the card
replaces the raw text). Thread it through as `SearchCardModel.recovery` and
render it below the card at all three sites, only when truncated.
- SearchRow's fallback body was gated on `state === 'error'`, so a settled
non-error call with no card (a successful nested run_code sub-dispatch, a
legacy generic result) showed only its summary with content lost. Widen it to
any settled call with `search === null`.
- searchCardModel trusted the `files`/`paths` shape the host wire schema only
string-checks; a malformed known-kind frame would crash SearchBlock. Validate
the full shape and fall to the generic path on mismatch.
- SearchBlock's restored tail file header added a row without consuming a tail
slot, exceeding maxLines by one and overstating the hidden count. Make it
consume a slot so the visible count holds at maxLines and `hidden` stays exact.
Correct the fixture JSDoc (now genuinely exceeds the row cap) and the Agent Note
recovery-text claim, sync the ui-conversation bilingual README with the search
row, and add an assembled keyless snapshot (apps/web/tests/search-card.snapshot.ts)
that pins the grep card's shape from the built bundles.
2026-07-30 22:40:07 +08:00
|
|
|
kind=matches
|
|
|
|
|
summary=显示 9 / 共 42 处匹配 · 3 个文件
|
|
|
|
|
file=packages/client/ui-primitives/src/SearchBlock.tsx3
|
|
|
|
|
file=packages/client/ui-conversation/src/client/toolviews/search-row.tsx4
|
|
|
|
|
line=16: export const DEFAULT_SEARCH_MAX_LINES = 16
|
|
|
|
|
line=138: export function SearchBlock(props: SearchBlockProps) {
|
|
|
|
|
line=141: const [collapsed, setCollapsed] = useState<ReadonlySet<number>>(() => new Set())
|
2026-07-31 17:23:14 +08:00
|
|
|
line=35: const search = searchCardModel(block)
|
|
|
|
|
line=52: search={search}
|
|
|
|
|
line=73: ctx.slots.register({ name: 'conversation.chat.toolview', key: 'grep', locale: NS }, SearchRow)
|
2026-07-31 16:34:16 +08:00
|
|
|
expand=… 其余 4 行
|
|
|
|
|
recovery=Found 9 of 42 matches
|
|
|
|
|
|
|
|
|
|
packages/client/ui-primitives/src/SearchBlock.tsx
|
|
|
|
|
Line 16: export const DEFAULT_SEARCH_MAX_LINES = 16
|
|
|
|
|
Line 138: export function SearchBlock(props: SearchBlockProps) {
|
|
|
|
|
Line 141: const [collapsed, setCollapsed] = useState<ReadonlySet<number>>(() => new Set())
|
|
|
|
|
packages/client/ui-conversation/src/client/contract/search-card-model.ts
|
|
|
|
|
Line 24: export const CHAT_SEARCH_MAX_LINES = 8
|
|
|
|
|
Line 60: export function searchCardModel(block: ToolCallBlock): SearchCardModel | null {
|
|
|
|
|
packages/client/ui-conversation/src/client/toolviews/search-row.tsx
|
2026-07-31 17:23:14 +08:00
|
|
|
Line 33: export function SearchRow({ toolName, block, inspect, t }: SearchRowProps) {
|
|
|
|
|
Line 35: const search = searchCardModel(block)
|
|
|
|
|
Line 52: search={search}
|
|
|
|
|
Line 73: ctx.slots.register({ name: 'conversation.chat.toolview', key: 'grep', locale: NS }, SearchRow)
|
2026-07-31 16:34:16 +08:00
|
|
|
|
|
|
|
|
(Full grep result stored at: fixture://spill/grep-68. Read it to see every match.)
|