# Correction offer

A suggested correction for what the person typed: "Did you mean ada@gmail.com?", from a check of the app's (a typo in an email's domain, an address the post office knows better) or a model's. Nothing changes until the person presses Use: no value is applied for them, and nothing is chosen in advance. Keep it mounted under its field and give it a `suggestion` when there is one (`null` when there isn't), so an offer that comes while the person works arrives and is announced once, politely.

The suggestion and the original may be a model's or the person's text: each is drawn as plain text in an isolate of its own, so bidi controls in it can't reorder the sentence. Pass `fieldId` so focus goes back to the field when the offer is taken or dismissed and leaves the page.

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

## Install

```bash
npx candor-ui add correction-offer
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/correction-offer`.
It brings in: bidi, button, locale, motion, tokens, use-announcer, utils.

## Example

```tsx
"use client"

import * as React from "react"
import { Field } from "@/components/candor/ui/field"
import { Input } from "@/components/candor/ui/input"
import { CorrectionOffer } from "@/components/candor/ui/correction-offer"

const typos: Record<string, string> = { "gmial.com": "gmail.com", "gamil.com": "gmail.com", "outlok.com": "outlook.com" }

// The app's check runs when the person leaves the field; the offer stays mounted and gets a suggestion when there is
// one. Nothing changes until the person presses Use.
export default function Example() {
  const [email, setEmail] = React.useState("ada@gmial.com")
  const [checked, setChecked] = React.useState<string | null>(null)
  const [dismissed, setDismissed] = React.useState<string | null>(null)
  const domain = checked?.split("@")[1] ?? ""
  const fixed = Object.hasOwn(typos, domain) ? `${checked?.split("@")[0]}@${typos[domain]}` : null
  const suggestion = fixed && checked === email && dismissed !== email ? fixed : null
  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <Field label="Email" id="offer-email">
        <Input type="email" value={email} onChange={(e) => setEmail(e.target.value)} onBlur={() => setChecked(email)} />
      </Field>
      <CorrectionOffer
        original={email}
        suggestion={suggestion}
        fieldId="offer-email"
        onAccept={() => suggestion && setEmail(suggestion)}
        onDismiss={() => setDismissed(email)}
      />
    </div>
  )
}
```

## <CorrectionOffer>

Props:

- `original: string`: What the person typed.
- `suggestion: string | null`: The suggested value, or `null` while there is none (nothing is drawn). May be a model's text.
- `reason?: string | undefined`: Why it is suggested, in a short line: "Most addresses at this company use gmail.com". May be a model's text, as the suggestion may.
- `onAccept: () => void`: The person chose the suggestion: apply it to the field. Nothing is applied before this.
- `onDismiss?: (() => void) | undefined`: The person kept what they typed. Left out, the offer has no Keep button and stays until the app takes it away.
- `fieldId?: string | undefined`: The field the offer is about: focus goes back to it when the offer is taken or dismissed.
- `className?: string | undefined`: Classes for the offer's box.

## Strings (`correctionOffer`)

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

- `offer`: Did you mean {suggestion}?
- `announce`: Did you mean {suggestion}? Accept or dismiss it after the field.
- `accept`: Use {suggestion}
- `dismiss`: Keep {original}

## Motion

- **offer** (arrive, rise): The offer arrives when a suggestion appears or changes after it was on screen; one there with the page is still.

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