# Build the "Scoped region edit" pattern: `RegionEditor`

## 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
Constrain generation to a selected region, layer, or range so the rest is guaranteed untouched.

Use it when: Image, video, code and document editing where the user has already accepted most of the output.

## What to build
An editing surface that lets a person select one region of an approved asset (an image area, a layer, a code or text range), describe a change, and regenerate only that region. It shows proof that everything outside the selection is unchanged, and lets the person keep or discard the patch. It renders inside an image, document or code editor.

### Props
- `asset`: { id: string; version: string; previewUrl?: string }, the approved original the edit is applied to.
- `regions`: Array<{ id: string; label: string; bounds: string; sizeLabel?: string }>, selectable targets, where `bounds` is a box, layer id or character range the parent understands.
- `selectedRegionId?`: string and `onSelectRegion(id: string | null)`, for controlled selection.
- `instruction`: string and `onInstructionChange(v: string)`, the change described for the selected region.
- `status`: "idle" | "selected" | "generating" | "review" | "error", controlled by the parent.
- `result?`: { regionId: string; changedLabel: string; outsideChanged: number; patchPreviewUrl?: string }, where `outsideChanged` is the count of pixels, tokens or characters changed outside the selection, computed by diffing the composite against the original.
- `onGenerate(regionId: string, instruction: string)`: asks the parent to generate a patch for that region only. The component never regenerates or composites anything itself.
- `onKeep()`, `onDiscard()` and `onCompareToggle(showOriginal: boolean)`: callbacks for the review step.

### States it must render
- Idle: the asset with its version label; the instruction field and Generate are disabled, and a note reads "Nothing selected. Everything is locked."
- Selected: the chosen region is outlined, the rest is dimmed and marked locked, and the panel names the region and its size, e.g. "Only "Mug" will be regenerated."
- Generating: a working indicator sits on the selected region only, Generate shows "Generating in mug" and other regions cannot be selected.
- Review: the patch is shown in place with a receipt listing "Changed: Mug only (84 x 84 px)" and "Outside it: 0 pixels changed, identical to v2", plus Keep, Discard and a Show original toggle; the version label reads "v2 + unsaved edit".
- Leak detected: when `outsideChanged` is above 0, the receipt turns red and states how much changed outside the selection, and Keep requires a second confirmation.
- Error: the original stays untouched and visible, with an inline message and Retry.

### Interactions
- Clicking a region (or pressing Enter on it) selects it; clicking it again clears the selection.
- Generate calls `onGenerate` with the selected region id and instruction; it is disabled with no selection or an empty instruction.
- Show original swaps the view to the approved version and back, calling `onCompareToggle`, so the person can check the surroundings themselves.
- Keep calls `onKeep` (the parent saves a new version); Discard calls `onDiscard` and restores the approved view.

### Edge cases
- Overlapping or nested regions: selecting one outlines only that one and names it; never merge them silently.
- The parent cannot compute `outsideChanged`: show "Unchanged outside: not verified" instead of a reassuring zero.
- Several regions edited in sequence before saving: list each changed region in the receipt, and keep the outside count relative to the approved original.
- A very small region (a few pixels or one token): keep a minimum hit target and show its size so the person knows it is tiny.

## Must avoid
Claiming a scoped edit but re-rendering the whole asset, quietly changing things the user had approved.

## Engineering constraint
Composite the patch client-side over the original; keep the untouched pixels or tokens byte-identical.

## Accessibility
- Regions are toggle buttons with `aria-pressed` and labels like "Select mug, 84 by 84 pixels"; locked areas are described in text, not only by dimming.
- Announce the review receipt (what changed and the outside count) when generation finishes, and move focus to it.

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