# Build the "Interrupt and kill switch" pattern: `RunStopControl`

## 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 always-present stop that halts the run, states what already happened, and offers to undo it.

Use it when: Long multi-step runs, and anything touching production systems.

## What to build
A stop control that stays visible for the whole run and, once pressed, shows a stopped panel listing what already happened, what was cancelled and what was never touched, with options to undo or resume from the checkpoint. It shows "Stopped" only after the executor confirms. It renders in the header of a run view or agent session.

### Props
- `status`: "running" | "stopping" | "stopped" | "finished" | "stop-failed", controlled by the parent from executor events.
- `onStop()`: requests cancellation. The component never assumes the run has stopped; it waits for `status`.
- `shortcut?`: string, the keyboard shortcut shown next to Stop, e.g. "Esc".
- `progress?`: { done: number; total?: number; unitLabel: string; current?: string }, e.g. 64 of 140 tickets closed.
- `stopReport?`: { stoppedBy: string; at: Date; confirmedByExecutor: boolean; effects: Array<{ label: string; count: number; reversible: boolean; note?: string }>; cancelledCall?: string; untouched: number; checkpointId?: string }.
- `onUndo?(effectLabel: string)`: reverses a reversible effect, e.g. reopen closed tickets.
- `onResume?(checkpointId: string)`: continues from the checkpoint instead of starting over.

### States it must render
- Running: a red-outlined Stop button with its shortcut, always in view (sticky), next to the live progress, e.g. "64 of 140 closed".
- Stopping: Stop is disabled with "Stopping, waiting for current call to finish"; progress freezes but the panel does not claim the run has halted.
- Stopped: a panel "Here's what already happened" listing each effect with reversibility ("64 tickets closed, can reopen", "64 emails sent, can't unsend"), the cancelled call ("close_ticket #5126 cancelled before it ran"), untouched items with "checkpoint saved", and who stopped it and when, with "executor confirmed".
- Stop failed: a red alert "The run did not stop" with a retry and an escalation path, never a silent return to Running.
- Finished: Stop is replaced by a completion line, e.g. "Finished: 140 tickets closed, 140 emails sent".

### Interactions
- Stop and its shortcut call `onStop` once with no confirmation dialog; pressing again while stopping does nothing.
- Each reversible effect has its own Undo button, labelled with the count, e.g. "Reopen the 64 tickets"; irreversible ones show why they can't be undone.
- Resume calls `onResume(checkpointId)` and is labelled with where it continues, e.g. "Resume from ticket 65".

### Edge cases
- Calls that complete between pressing Stop and confirmation: they appear in the effects list, not in "untouched".
- A run with no checkpoint: hide Resume and say "This run can't resume; restart it to continue".
- Stop pressed in the last moments as the run finishes: show Finished with a note that it completed before the stop took effect.
- The shortcut must not fire while focus is in a text input that uses the same key.

## Must avoid
A stop button that only hides the UI while the agent keeps executing tool calls in the background.

## Engineering constraint
Propagate cancellation to tool executors; record a checkpoint so the user can resume rather than redo.

## Accessibility
- Stop is a real button with an accessible name including its shortcut; it stays in the tab order and reachable for the whole run.
- Announce Stopping, then Stopped with the effect counts, and move focus to the stopped panel heading.

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