# Build the "Return to agent" pattern: `ResumeFromReview`

## 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
After human work, the agent resumes with the correction folded in and says what it now knows.

Use it when: Approval-gated pipelines and any loop where the human is a step, not the terminus.

## What to build
The review step of a paused agent run: it shows the checkpoint, which steps are already done and will be kept, the output to correct, and an optional note. After the person sends it back, it shows the agent's acknowledgement of what changed and which step it resumes from. It renders in a run view or an approvals inbox.

### Props
- `steps`: Array<{ id: string; title: string; detail?: string; state: "done" | "waiting" | "pending" | "running" | "will-rerun" }>.
- `checkpoint`: { afterStepId: string; savedAt: Date } and `resumeFromStepId`: string.
- `fields`: Array<{ id: string; label: string; value: string; original: string; validate?: (v: string) => string | null }>, the output the reviewer may correct.
- `note`: string and `onNoteChange(note: string)`, an optional instruction for the agent.
- `decisionId`: string, sent with every submission so resuming is idempotent.
- `status`: "awaiting-review" | "submitting" | "resumed" | "already-decided" | "error", controlled by the parent.
- `acknowledgement?`: { kept: string[]; changed: Array<{ label: string; from: string; to: string }>; learned?: string }, the agent's reply after resuming.
- `decidedBy?`: { name: string; at: Date }.
- `onSendBack({ decisionId, fields, note })`, `onApprove(decisionId)` and `onFieldChange(id, value)`: callbacks. The component never restarts the run.

### States it must render
- Awaiting review: the step list with done steps ticked, the paused step highlighted, "Checkpoint saved after step 3. Nothing sent.", and the editable fields.
- Edited: changed fields show the original struck through beside the new value; Send back shows "Resumes at step 5".
- Submitting: actions disabled with a busy indicator.
- Resumed: the acknowledgement, e.g. "Kept from before: seat usage and 14 tickets, not re-run. Changed: seats 40 → 45. Continuing at step 5."
- Already decided: "Sent back by" with the reviewer's name and time with no actions, when the same decision arrived from elsewhere.
- Error: "Couldn't resume. Your edits are kept." with Retry using the same `decisionId`.

### Interactions
- Editing a field calls `onFieldChange`; Send back calls `onSendBack` only when every `validate` returns null.
- Approve as is calls `onApprove`.
- A second click while submitting does nothing.

### Edge cases
- An edit that invalidates an earlier step: that step is shown as `will-rerun` with the reason, so the person sees exactly what repeats.
- Checkpoint too old to resume: say so and offer the parent's restart path as a separate, clearly labelled action.
- Retrying after a network error reuses `decisionId`, so the run can't resume twice.
- No changes and no note: Send back is replaced by Approve as is.

## Must avoid
Requiring a full restart after approval, wasting the run and the reviewer's decision.

## Engineering constraint
Persist run state keyed to the approval so resume is idempotent and replay-safe.

## Accessibility
- The step list is an ordered list with each state in text; the paused step has `aria-current="step"`.
- After resuming, move focus to the acknowledgement.

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