# Entity disambiguation

When a name matches more than one thing ("Account" the table, the report, the record type), the agent asks which one instead of guessing. Each candidate shows its kind, API name and what sets it apart. "None of these" is always offered. Once chosen, the card shows the answer.

- Registry name: `entity-disambiguation` (`@candor/entity-disambiguation`)
- 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/entity-disambiguation

## Install

```bash
npx candor-ui add entity-disambiguation
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/entity-disambiguation`.
It brings in: chat, entity-chip, locale, terms, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"use client"

import * as React from "react"
import { EntityDisambiguation, type EntityCandidate } from "@/components/entity-disambiguation"
import type { EntityRef } from "@/lib/chat"

const candidates: EntityCandidate[] = [
  { entity: { kind: "table", name: "public.orders", label: "orders" }, detail: "Table in the main database, 1.2M rows" },
  { entity: { kind: "report", name: "reports/orders-weekly", label: "Orders" }, detail: "Weekly report in the Finance folder" },
  { entity: { kind: "workflow", name: "flows.orders_sync", label: "Orders sync" }, detail: "Runs every hour on Staging" },
]

export default function Example() {
  const [chosen, setChosen] = React.useState<EntityRef | null | undefined>(undefined)
  return (
    <div className="flex max-w-lg flex-col gap-3">
      <EntityDisambiguation term="orders" candidates={candidates} onChoose={setChosen} onNone={() => setChosen(null)} chosen={chosen} />
      {chosen !== undefined && (
        <button type="button" onClick={() => setChosen(undefined)} className="self-start text-xs text-muted-foreground underline underline-offset-2">
          Ask again
        </button>
      )}
    </div>
  )
}
```

## <EntityDisambiguation>

Props:

- `term: string`: What the person typed, e.g. "Account".
- `candidates: EntityCandidate[]`: The things the term could mean, with kind, API name and what sets each apart.
- `onChoose: (entity: EntityRef) => void`: Called with the chosen entity.
- `onNone?: (() => void) | undefined`: Shows "None of these"; called when it's picked.
- `chosen?: EntityRef | null | undefined`: The settled choice, if one was made.
- `className?: string | undefined`

## Strings (`entityDisambiguation`)

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

- `means`: “{term}” means
- `none`: None of the matches for “{term}”; the agent will ask what you meant.
- `question`: { one: "“{term}” matches {count} thing in the {environment}. Which one?", other: "“{term}” matches {count} things in the {environment}. Which one?", }
- `noneOfThese`: None of these

## Types

### EntityCandidate

```ts
{ entity: EntityRef /** What tells it apart: "Standard object, 18,240 records", "Report in Sales folder". */ detail: string }
```

## 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` (fixed strings) and `TermsProvider` (nouns); entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
