# Build the "Dry run preview" pattern: `DryRunPlan`

## 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
Show the full sequence the agent intends to execute, with side effects marked, before anything runs.

Use it when: Bulk operations, migrations, and workflows a user cannot easily inspect afterwards.

## What to build
A read-only preview of every step a run would execute, with each side effect labelled and the totals summarised (money moved, messages sent, records deleted), before anything runs. It is rendered from the same plan object the executor will consume, and after the run it turns into a receipt that confirms each step matched. It renders above the Run button of a bulk operation or workflow.

### Props
- `plan`: { id: string; steps: Array<{ id: string; title: string; detail: string; effect: "read" | "write" | "money" | "external-message" | "delete"; reversible: boolean | string; optional?: boolean; included: boolean }> }, the exact plan object the executor runs, including its id or hash.
- `effectTotals`: Array<{ label: string; value: string }>, e.g. "Credits issued: $4,320", "Customer emails: 36", "Records deleted: 0".
- `onToggleStep?(stepId: string, included: boolean)`: for optional steps; the parent returns a new plan with a new id.
- `status`: "preview" | "running" | "done" | "diverged" | "error", controlled by the parent.
- `stepResults?`: Record<string, { state: "running" | "done" | "skipped" | "failed"; detail: string; matchesPreview: boolean }>.
- `onRun(planId: string)`: runs exactly that plan id. The component never executes anything itself.

### States it must render
- Preview: a badge "Dry run, nothing changed", numbered steps each with an effect label ("Read only", "Moves money", "Emails customers") and reversibility ("can't be unsent"), the totals box, and the plan id, e.g. "plan 7f3a, 5 steps".
- Step excluded: an optional step is struck through with "Removed from the plan", totals update, and the footer says "Plan updated to 7f3b: emails removed, nothing else changed".
- Running: steps tick through running and done in order, each with its actual result.
- Done: the totals box becomes "Receipt for plan 7f3a", each step shows its result next to the preview ("38 accounts, same as preview"), and a green line "Every step ran exactly as previewed".
- Diverged: execution stopped at the step where live data no longer matched, showing preview and actual side by side and asking the person how to continue.
- Error: the failed step is marked, later steps show "Not run".

### Interactions
- Run calls `onRun(plan.id)`; the button label names the plan, e.g. "Run this exact plan".
- Toggling an optional step calls `onToggleStep`; Run stays disabled until the parent returns the updated plan, so the preview and the executed plan never differ.
- Expanding a step shows its full detail (affected records, template, amounts).

### Edge cases
- The plan id passed to `onRun` must be the one currently displayed; if `plan` changes while the person is reading, highlight what changed and require a fresh look before Run.
- Steps whose counts depend on live data: show the count at preview time and state that the run stops and asks if it differs.
- No side effects at all: say "This run only reads data" in the totals.
- Irreversible effects (external messages, deletes) are labelled red and listed first in the totals.

## Must avoid
A plan that diverges from execution, teaching users the preview is decorative.

## Engineering constraint
Generate the preview from the same plan object the executor consumes, or it will drift.

## Accessibility
- Steps are an ordered list; each step's effect and reversibility are in its text, not only in colour or icon.
- Announce Done or Diverged, including the matched or mismatched step.

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