# Build the "Completion receipt" pattern: `RunReceipt`

## 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 closing summary of what changed, what was skipped, and what still needs a human.

Use it when: Any run with side effects the user did not watch.

## What to build
A closing summary for a run the person didn't watch: what changed, what was skipped and why, and what still needs a human, built from recorded effects rather than the model's own account. Each change links to its undo where possible, and items needing a person are actionable in place. It renders at the end of a run log, in an inbox or as a notification detail view.

### Props
- `run`: { title: string; finishedAt: Date; durationLabel: string; startedBy: string; scopeLabel?: string; recordedActionCount: number }, e.g. "checked 1,906 charges, built from 9 recorded actions".
- `changed`: Array<{ id: string; label: string; detail?: string; value?: string; undoUntil?: Date; undone?: boolean }>.
- `skipped`: Array<{ id: string; label: string; reason: string }>.
- `needsHuman`: Array<{ id: string; label: string; reason: string; actions: Array<{ id: string; label: string }> }>.
- `totals?`: Array<{ label: string; value: string }>, e.g. "5 refunded, $6,210".
- `onUndo(changeId: string)` and `onNeedsAction(itemId: string, actionId: string)`: callbacks. The component never performs the actions itself.
- `activeTab`: "changed" | "skipped" | "needs" | "log" and `onTabChange`, controlled.
- `logSlot?`: React.ReactNode, the full step log.

### States it must render
- Needs you: a header badge "1 needs you" in amber, tab counts ("5 changed", "2 skipped, with reasons", "1 needs you"), and the needs tab opened by default.
- Changed: each change with its value and an Undo button, plus a note like "Each refund can be reversed for 24 hours. Customers were not emailed yet."
- Skipped: each skipped item with a specific reason, e.g. "Already refunded on 12 Aug. Nothing to do."
- Undone: the change is struck through with "Undone by you" and totals recalculate.
- All settled: once needs is empty, the badge turns green "All settled".
- Nothing changed: "The run finished without changing anything" with skipped reasons, never a bare "Done".

### Interactions
- Tabs switch lists via `onTabChange`; arrow keys move between tabs.
- Undo calls `onUndo(id)`; it is hidden after `undoUntil` and replaced by "Can no longer be undone".
- Needs-human actions call `onNeedsAction`; resolved items move into changed or skipped as the parent updates the props.

### Edge cases
- Long manifests (dozens of files or records): group by type with counts and expand, but never replace the list with a total alone.
- A recorded effect that failed partway: list it under needs human with what did and didn't happen.
- Undo windows that expire while open: update the button state without a reload.
- Counts in totals must equal the list lengths; if they don't, show the list and flag the mismatch.

## Must avoid
Ending with "Done!" and no manifest of the twelve files it edited.

## Engineering constraint
Build the receipt from recorded effects, not from asking the model what it thinks it did.

## Accessibility
- Use a `tablist` with counts in each tab's accessible name; the needs tab is announced first.
- After Undo, announce what was undone and the new total.

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