Search trace
ProA search an agent ran, as a tool-call row: what it did in plain words, its status and how long it took, and a count of the searches and the sources read. Opened, it lists each query the agent ran and, under it, the sources it read: each one's title and its domain, as text, never a link. When the tool says the agent read more than it listed, a line says how many; when it doesn't say, nothing is guessed. A failed search opens by default and leads with its error, as ToolCall does.
Built on tool-call's data: pass the same ToolCallData your adapter gives you for the search, with the queries and sources read from its input and output.
Version 5.2.0· other versions
This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).
Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1, 2.6.2, 2.7.0, 2.8.0 and 2.9.0: the page came later, or had another name then.
Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.
Preview
Loading the preview
Install
First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/search-trace
Brings in: bidi, chat, citation, collapsible, locale, locked, motion, status, step-list, streaming-text, tokens, utils · npm: lucide-react@^1.67.0
Usage
"use client"
import { SearchTrace } from "@/components/candor/search-trace"
export default function Example() {
return (
<div className="flex max-w-lg flex-col gap-1">
{/* Sources show their title and domain as text, never a link; "more" says how many it read beyond those listed. */}
<SearchTrace
defaultOpen
call={{ id: "s1", tool: "web.search", title: "Searched the web", status: "succeeded", duration: "3.1s" }}
queries={[
{
id: "q1",
query: "card network chargeback time limit 2026",
sources: [
{ id: "a", title: "Dispute time limits for merchants", address: "https://www.example.com/merchants/disputes" },
{ id: "b", title: "Chargeback reason codes, explained", address: "https://docs.example.org/chargebacks/codes" },
],
more: 4,
},
{
id: "q2",
query: "refund policy settled orders",
sources: [{ id: "c", title: "Refunds and returns", origin: "Help centre · Policies", kind: "knowledge" }],
},
]}
/>
{/* While it runs, nothing is said about a search that has read nothing yet. */}
<SearchTrace
call={{ id: "s2", tool: "web.search", title: "Searching the web", status: "running" }}
queries={[{ id: "q3", query: "carrier delivery exceptions Rotterdam", sources: [] }]}
/>
</div>
)
}<SearchTrace>
| Prop | Type | Default | Description |
|---|---|---|---|
| callrequired | ToolCallData | — | The search's tool call: its plain-words title ("Searched the web"), status, duration and error. Its input and output aren't shown; `queries` holds what they said. |
| queriesrequired | SearchQuery[] | — | The queries the agent ran, in order, each with the sources it read. |
| defaultOpen | boolean | undefined | — | Whether it starts open. Defaults to open when the call failed, closed otherwise. |
| className | string | undefined | — | Extra classes for the outer element. |
Functions
sourceDomain(address: string | undefined)The domain of a source's address, without a leading "www.", as the URL parser reads it; undefined when the address isn't a URL with a host.
Strings
The fixed words, in English by default. Change any of them under searchTrace in <LocaleProvider strings={{ … }}>; the table is exported as searchTraceStrings.
| Key | English |
|---|---|
| lockedNoDetail | This search has no details to show. |
| searches | { one: "{count} search", other: "{count} searches" } |
| sources | { one: "{count} source read", other: "{count} sources read" } |
| status | — {status} |
| domainSeparatorSpoken | , |
| query | “{query}” |
| sourcesFor | Sources read for “{query}” |
| more | { one: "and {count} more it read", other: "and {count} more it read" } |
| nothingRead | Read nothing for this search |
Motion
| Moment | Family | What it does |
|---|---|---|
| open | reveal, size | The searches open to their full height when the person opens the trace, and the chevron turns with them; closing reverses both. Under reduced motion it snaps open or shut. |
Set with --cd-motion-search-trace-<moment>-* in your brand file; the families are in the design reference.
Types
- SearchSource
- One source the agent read: the page or document's title as the tool reported it, and where it lives, as text.
{ id: string; /** The page's title or the document's name, as the tool reported it. Shown as text. */ title: string; /** * The source's address as the tool reported it. Only its domain is shown ("example.com", without "www."), read by * the browser's own URL parser, so an address that hides its host in a user name shows the real host and an * internationalised one shows its xn-- form. It is never a link. */ address?: string | undefined; /** Where it lives in the app's words, for a source with no web address ("Docs · Refund policy"). Shown when `address` has no domain. */ origin?: string | undefined; /** What kind of source it is, for its icon. Defaults to "web". */ kind?: SourceKind | undefined; }- SearchQuery
- One query the agent ran and what it read for it.
{ id: string; /** The query exactly as the agent ran it. Model output: shown as text. */ query: string; /** The sources the agent read for this query, in the order it read them. */ sources: SearchSource[]; /** How many more sources the tool says the agent read for this query than `sources` lists. Left out, nothing is said about more. */ more?: number | undefined; }
Types from other items
The props above take these. Each is defined by another item; import it from the path shown.
- ToolCallData from @/lib/candor/chat
{ id: string; /** The tool's name, for people who want it: "metadata.describe". */ tool: string; /** What it did, in plain words: "Read Account's fields". */ title: string; status: RunStatus; /** Shown when expanded. Text only; long values scroll. */ input?: string | undefined; output?: string | undefined; outputLanguage?: PayloadLanguage | undefined; /** e.g. "1.2s". Absent while running; never estimated. */ duration?: string | undefined; /** Required when status is "failed": what went wrong, in a sentence. */ error?: string | undefined; /** * The lines it changed, when it edited files and the tool counted them: shown as "+12 −3". Left out when the tool * doesn't say; never estimated. New in 4.4. */ lines?: { added: number; removed: number; } | undefined; /** * Who let it run, when it needed a go-ahead: a person, or something automatic (a policy, a classifier, a rule). * `note` is why, in the source's words. Left out when it needed none or the source doesn't say. */ approval?: { by: "person" | "automatic"; note?: string | undefined; /** The input as the agent proposed it, when a person edited it before it ran; `input` is then what ran. */ originalInput?: string | undefined; } | undefined; }- SourceKind from @/lib/candor/chat
- "document", "web", "query", "code", "data", "workflow", "knowledge", or the app's own; unknown kinds read as "Source".
string- RunStatus from @/lib/candor/status
- The status vocabulary every Candor agent component shares. Components take plain data in these shapes rather than an SDK's message objects, so they work with your own LLM drivers, the Vercel AI SDK or anything else; adapters map an SDK's parts onto them.
"idle" // not started | "queued" // accepted, waiting for a worker | "running" // an agent is working on it now | "waiting" // stopped to ask a person (approval, question) | "blocked" // can't start: something it depends on failed or is waiting | "succeeded" | "failed" | "cancelled" | "skipped"- PayloadLanguage from @/lib/candor/chat
- The block's language, e.g. "json", "sql", "python", "text". Shown as a label; never executed or highlighted as HTML.
string