# Proposal

One action an agent proposes, with the alternatives it weighed beside it. Each option shows how sure the agent is through ConfidenceMark, with the basis on hover and for screen readers, and unknown when the agent didn't say. The proposal is marked, but nothing is chosen for the person: Accept (and Edit) say they need a choice until the person picks one, the proposal or an alternative, and Accept then calls back with that option. With `onEdit`, Edit hands the chosen option to the app to change before it is accepted; with `onDecline`, "None of these" turns them all down. Once accepted or declined, the options stay on screen with the chosen one marked, and a polite status line says what the person did.

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

## Install

```bash
npx candor-ui add proposal
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/proposal`.
It brings in: ai-content-label, bidi, button, confidence-mark, heading-level, heading-scope, know, locale, locked, radio-group, tokens, utils.
npm packages: lucide-react@^1.67.0.

## Example

```tsx
"use client"

import * as React from "react"
import { Proposal, type ProposalOption } from "@/components/candor/proposal"

const proposed: ProposalOption = {
  id: "refund-duplicate",
  label: "Refund the duplicate charge",
  detail: "Returns $84.20 to the card the second payment came from. The first payment stands.",
  reason: "Both charges have the same amount, card and basket, 40 seconds apart.",
  confidence: { value: "high", basis: "The payment service lists both charges with one basket id" },
}

const alternatives: ProposalOption[] = [
  {
    id: "credit",
    label: "Add $84.20 as store credit",
    detail: "Nothing goes back to the card; the customer can spend it on the next order.",
    reason: "Faster to settle, but the customer asked for their money back.",
    confidence: { value: "low", basis: "Nothing in the conversation says the customer would accept credit" },
  },
  {
    id: "ask-bank",
    label: "Ask the customer for a bank statement first",
    reason: "Would confirm the second charge settled, at the cost of a day or two.",
  },
]

export default function Example() {
  const [log, setLog] = React.useState("")
  return (
    <div className="flex max-w-xl flex-col gap-2">
      {/* Nothing is chosen until the person chooses: Accept says so until they pick the proposal or an alternative. */}
      <Proposal
        agent="Billing agent"
        title="Order #4471 was charged twice"
        proposed={proposed}
        alternatives={alternatives}
        onAccept={(o) => setLog(`Accepted: ${o.id}`)}
        onEdit={(o) => setLog(`Editing: ${o.id}`)}
        onDecline={() => setLog("Declined")}
      />
      <p className="text-xs text-muted-foreground">{log || "Nothing decided yet."}</p>
    </div>
  )
}
```

## <Proposal>

Props:

- `proposed: ProposalOption`: The action the agent proposes. It is marked, never pre-selected.
- `alternatives: ProposalOption[]`: The other actions the agent weighed, in the order it ranked them.
- `onAccept: (option: ProposalOption) => void`: Called with the option the person chose when they press Accept. Carry it out, or send it to the agent; the component changes nothing itself.
- `onEdit?: ((option: ProposalOption) => void) | undefined`: Adds Edit, when the app lets the person change an option before accepting it. Called with the chosen option; open it in your editor.
- `onDecline?: (() => void) | undefined`: Adds "None of these"; called when the person turns every option down.
- `agent?: string | undefined`: The agent that proposes it, named by the app ("Billing agent"). Never model text.
- `title?: string | undefined`: What the decision is about, in the app's words ("Order #4471 was charged twice"). Defaults to "Proposed action".
- `locked?: string | null | undefined`: Why the person can't decide (they can view but not act, say). Accept, Edit and "None of these" stay visible, locked, and say so: declining is a decision too. `null` or left out: unlocked.
- `headingLevel?: HeadingLevel | undefined`: The heading's level. Defaults to one below the nearest `HeadingScope`, else 2.
- `className?: string | undefined`

## Strings (`proposal`)

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

- `heading`: Proposed action
- `proposes`: {agent} proposes
- `proposesUnnamed`: The agent proposes
- `proposed`: Proposed
- `alternatives`: Alternatives it weighed
- `choose`: Choose an action
- `chooseFirst`: Choose one of the options first.
- `reasonLabel`: Its reasoning, not checked:
- `noBasis`: The agent didn’t say how sure it is.
- `accept`: Accept
- `edit`: Edit
- `decline`: None of these
- `untouched`: Nothing is chosen until you choose.
- `chosen`: Chosen
- `accepted`: You accepted “{label}”.
- `acceptedAlternative`: You accepted “{label}”, an alternative to the proposal.
- `declined`: You declined every option.
- `editing`: “{label}” is open for you to edit.

## Types

### ProposalOption

One action the agent weighed. Model output: every word is shown as text, never as HTML or a link.

```ts
{
  /** Stable for this option; Accept and Edit call back with the option. */
  id: string;
  /** The action in plain words: "Refund the order in full". */
  label: string;
  /** What it would do, in a sentence: "Returns $84.20 to the card it was paid with." */
  detail?: string | undefined;
  /** Why the agent ranks it where it does, in its own words. It is the agent's account, not a checked fact, and is shown after "Its reasoning, not checked:". */
  reason?: string | undefined;
  /** How sure the agent is that this is the right action, and what that rests on. Left out, it shows as unknown, with the agent giving no basis. */
  confidence?: {
    value: Confidence;
    basis: string;
  } | undefined;
}
```

## Types from other items

The props above take these. Each is defined by another item; import it from the path shown.

### HeadingLevel

Import: `import type { HeadingLevel } from "@/lib/candor/heading-level"` (item `heading-level`, https://candor.themethodic.com/docs/heading-level.md)

A heading's level in the page's outline: `1` is `<h1>`, `6` is `<h6>`. Every Candor item that draws a heading takes its `headingLevel` as this type; the level is where the heading sits in the outline, never how large it looks.

```ts
1 | 2 | 3 | 4 | 5 | 6
```

### Confidence

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

```ts
"high" | "medium" | "low" | "unknown"
```

## Motion

- **tint** (tint): An option's border and background change colour when the pointer is over it and when the person chooses it, and the radio's ring fades in on keyboard focus.

Set with `--cd-motion-proposal-<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`.
