# Build the "Approve with edits" pattern: `EditableProposal`

## 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 diff of the agent's proposal the user can amend before accepting, rather than accept-or-discard.

Use it when: Drafting and code flows where the output is 80% right and rejection wastes the work.

## What to build
A review view of the agent's proposed changes as separate hunks the person can accept, edit or drop individually before applying the whole set. The edited result is sent as a patch against the version the agent read, so nothing is regenerated. It renders inline in a chat, review panel or composer.

### Props
- `hunks`: Array<{ id: string; label?: string; before: string; after: string; decision: "pending" | "accepted" | "edited" | "dropped"; editedText?: string }>, the proposal split into reviewable changes.
- `baseVersion`: string, the version id the agent read.
- `currentVersion`: string, the latest version; a mismatch means the base is stale.
- `status`: "reviewing" | "applying" | "applied" | "conflict" | "error", controlled.
- `conflicts?`: Array<{ hunkId: string; reason: string }>, hunks that no longer apply cleanly.
- `onDecide(hunkId, decision, editedText?)`: callback per hunk.
- `onApply()`: apply all non-dropped hunks as they stand, and `onDiscard()`: drop the whole proposal. The component never applies changes itself.

### States it must render
- Reviewing: each hunk shows before and after with Accept, Edit and Drop; a footer says "3 changes: 1 edited, 1 dropped".
- Editing a hunk: the after text becomes an editable field pre-filled with the agent's text; the original suggestion stays visible above it.
- Applying: controls disabled with a busy indicator.
- Applied: a receipt listing what went out as written, what was edited and what was dropped.
- Conflict: hunks that clash with newer edits are marked with the reason and options to re-review against the latest version or drop them; clean hunks can still be applied.

### Interactions
- Accept, Edit and Drop call `onDecide`; a dropped hunk collapses to one line with Undo.
- Apply is enabled once every hunk has a decision, or offers "Apply and accept the rest" when some are pending.
- Keyboard: J and K move between hunks; A, E and D decide the focused hunk (shown in a hint).
- Discard asks for confirmation if any hunk was edited, since that work would be lost.

### Edge cases
- `baseVersion` differs from `currentVersion`: show "The text changed since this was proposed" before the person starts reviewing.
- All hunks dropped: Apply becomes "Close" and calls `onDiscard`.
- An edited hunk is emptied: treat as a deletion and say so, rather than applying an empty string silently.
- A single-hunk proposal: still offer Edit, never only Accept and Reject.

## Must avoid
All-or-nothing accept buttons that force a full regeneration over one wrong sentence.

## Engineering constraint
Apply as a patch against the version the agent read; detect and surface conflicts on stale bases.

## Accessibility
- Each hunk is a group labelled by its label or position ("Change 2 of 3") and decision; before and after are labelled in text, not just by colour.
- After applying, focus moves to the receipt.

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