# Build the "Confidence signalling" pattern: `ConfidenceFieldList`

## Stack (edit this block to match your project)
- React 18+ with TypeScript: one function component, typed props, no `any`.
- Tailwind CSS, using your design system's tokens if you have them.
- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.
- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.

## What the pattern is for
A visible estimate of certainty — level, band, or hedged language — so users know when to check.

Use it when: Classification, extraction, medical, legal and financial flows where silent certainty is dangerous.

## What to build
A list of AI-extracted or AI-classified values, each with a confidence level shown as a word and a band, plus a short reason for anything below high. It directs review to the uncertain values and can block a dependent action until they are checked. It renders beside a source document in extraction, classification or review flows.

### Props
- `fields`: Array<{ id: string; label: string; value: string; level: "high" | "medium" | "low"; reason?: string; alternatives?: string[]; sourceRef?: string }>, bucketed levels, not raw scores.
- `score?`: Record<string, number>, shown only when `calibrated` is true.
- `calibrated`: boolean, whether numeric scores are trustworthy enough to display; default false.
- `calibration?`: string, evidence for the levels, e.g. "Last 30 days: reviewers kept 98% of High, 81% of Medium, 54% of Low unchanged".
- `onResolve(fieldId: string, value: string, changed: boolean)`: logs accepted versus corrected, which the parent uses to test calibration.
- `onFocusSource?(sourceRef: string | null)`: highlights the region of the source the value came from.
- `blockingAction?`: { label: string; onConfirm(): void }, an action enabled only once medium and low fields are resolved.
- `status`: "loading" | "ready" | "error".

### States it must render
- Loading: field rows with placeholders; no level shown until values arrive.
- Ready: each row shows label, value and a level chip ("High", "Medium", "Low") using text plus shape; medium and low rows are expanded with the reason, e.g. "The last digit is blurred in the scan. It reads as 7, but could be 1."
- Needs checking: a header count "2 fields to check" and the blocking action disabled with "Check the 2 flagged fields to continue".
- Resolved field: the row shows "Kept by you" or "Changed by you" with the original value if different.
- All resolved: the header turns green and the blocking action is enabled.
- Error: "Couldn't extract these fields" with Retry.

### Interactions
- Low and medium rows offer "Keep <value>" and one button per alternative, plus a free-text edit; each calls `onResolve`.
- Focusing a row calls `onFocusSource` so the matching area of the document is highlighted.
- High rows can be expanded and edited too, but are not required.

### Edge cases
- Uncalibrated models must not show percentages: with `calibrated` false, scores are ignored even if passed.
- Confidence is not evidence: when `sourceRef` is missing, show "No source location" next to the level rather than letting the level stand alone.
- All fields high: show no checking prompt, and keep the calibration line visible.
- A field the model could not read at all: show "Not found" as low with an empty input, never a guessed value.

## Must avoid
A precise percentage from an uncalibrated model, or confidence used as a substitute for citations.

## Engineering constraint
Bucket into levels unless the score is genuinely calibrated; log accepted-vs-corrected to test it.

## Accessibility
- Each level chip has text, and the row's accessible description includes the level and reason.
- Announce the remaining count after each resolution, e.g. "1 field left to check".

## House rules
- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live="polite"`; respect `prefers-reduced-motion`.
- All user-facing text comes from props (or a `labels` prop) so it can be translated.
- One consistent accent marks anything the AI made or is doing; amber means "needs a person", red means risky, green means done. Never rely on colour alone.
- Fits a 360 px wide container without horizontal scrolling.

## Acceptance checks
- [ ] Every state above can be rendered by passing props, with no internal fake data.
- [ ] Every interaction and edge case above behaves as described.
- [ ] Nothing in "Must avoid" can happen with this component.

## Deliver
1. The component file with its exported prop types.
2. A short usage example that renders each state.
