# Build the "Scheduled agent runs" pattern: `ScheduledRunList`

## 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
Recurring autonomous work on a visible schedule, with results delivered into an existing surface.

Use it when: Monitoring, reporting and maintenance jobs where the value is in never being asked for.

## What to build
A list of recurring agent jobs, each showing its plain-language cadence, last run, next run, where results are delivered and whether anyone is reading them. A person can run, pause, resume, edit or end a schedule, and sees when the system auto-paused one. It renders as a settings page or a panel next to the surface that receives results.

### Props
- `schedules`: Array<{ id: string; title: string; instruction: string; cadence: string; deliverTo: string; status: "active" | "paused" | "auto-paused" | "ended" | "running"; lastRun?: { at: Date; outcome: "delivered" | "failed"; opened?: boolean; openedBy?: number }; nextRunAt?: Date; endsAt?: Date; endCondition?: string; autoPauseReason?: "unopened" | "failures"; costPerRun?: string }>.
- `selectedId?`: string and `onSelect(id | null)`.
- `onRunNow(id)`, `onPause(id)`, `onResume(id)`, `onEnd(id)` and `onEdit(id)`: callbacks. The component never runs or schedules work itself.
- `autoPausePolicy?`: string, shown in each row, e.g. "Pauses after 2 unopened results".
- `emptyAction?`: { label: string; onClick: () => void }, for creating the first schedule.

### States it must render
- Active: title, cadence ("Every Monday at 08:00"), destination, "Last run Mon 28 Sep, opened by 4" and "Next Mon 5 Oct, 08:00".
- Running: "Running now" with the destination it will land in; the next-run time stays visible.
- Auto-paused: amber row stating why ("Paused: the last 2 results were not opened" or "Paused after 3 failed runs"), with Resume and End; no next run is shown.
- Paused / ended: muted row with who paused it or the end date; ended schedules move to an "Ended" section.
- Empty: a short explanation of what schedules do with the create action.

### Interactions
- Run now calls `onRunNow` and the row shows Running; on delivery it shows "Delivered just now" with a link to the destination.
- Pause is one click from the row, never only inside an edit form.
- Resume on an auto-paused schedule asks for an end date or condition first (optional), so it does not run indefinitely again.
- Selecting a row expands the full instruction, end condition and cost per run.

### Edge cases
- A schedule with no end date or condition: show "No end date" as a visible attribute, not blank space.
- Last result delivered but not opened: show "Not opened" in the last-run line before auto-pause triggers.
- The next run falls inside a pause window or after `endsAt`: show "No more runs" instead of a date.
- Time zones: show times in the viewer's zone and include the zone when the schedule's own zone differs.

## Must avoid
Schedules that keep firing after the reason expired, producing output nobody reads and cost nobody sees.

## Engineering constraint
Show last-run and next-run; auto-pause on repeated unopened results or repeated failure.

## Accessibility
- Each row is a `listitem` whose name includes title, status and next run; auto-pause reasons are in text, not only an icon or colour.
- After Run now, announce "Running" and then "Delivered to <destination>".

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