# Search trace

A 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 `ToolCall` your adapter gives you for the search, with the queries and sources read from its input and output.

- Registry name: `search-trace` (`@candor/search-trace`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/search-trace

## Install

```bash
npx candor-ui add search-trace
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/search-trace`.
It brings in: bidi, chat, citation, collapsible, locale, motion, status, step-list, streaming-text, tokens, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"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>

Props:

- `call: ToolCall`: 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.
- `queries: 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`

## Functions

### sourceDomain

```ts
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 (`searchTrace`)

The fixed words, English by default. Override any of them under `searchTrace` in `<LocaleProvider strings={{ searchTrace: { … } }}>`; the table is exported as `searchTraceStrings`.

- `searches`: { one: "{count} search", other: "{count} searches" }
- `sources`: { one: "{count} source read", other: "{count} sources read" }
- `counts`: {searches} · {sources}
- `status`: — {status}
- `domainSeparator`: ·
- `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

## Types

### SearchSource

One source the agent read: the page or document's title as the tool reported it, and where it lives, as text.

```ts
{
  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.

```ts
{
  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.

### ToolCall

Import: `import type { ToolCall } from "@/lib/candor/chat"` (item `chat`, https://candor.themethodic.com/docs/chat.md)

```ts
{
  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;
  /**
   * 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

Import: `import type { SourceKind } from "@/lib/candor/chat"` (item `chat`, https://candor.themethodic.com/docs/chat.md)

"document", "web", "query", "code", "data", "workflow", "knowledge", or the app's own; unknown kinds read as "Source".

```ts
string
```

### RunStatus

Import: `import type { RunStatus } from "@/lib/candor/status"` (item `status`, https://candor.themethodic.com/docs/status.md)

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.

```ts
"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

Import: `import type { PayloadLanguage } from "@/lib/candor/chat"` (item `chat`, https://candor.themethodic.com/docs/chat.md)

The block's language, e.g. "json", "sql", "python", "text". Shown as a label; never executed or highlighted as HTML.

```ts
string
```

## Motion

- **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.

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
