# Build the "Madlib prompt" pattern: `MadlibPrompt`

## 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 sentence with editable slots, so the grammar of a good request is visible and the user just fills blanks.

Use it when: Narrow generative tasks with two or three real variables and a novice audience.

## What to build
A request written as a sentence with a few editable slots, so the shape of a good prompt is visible and a person only fills the blanks. Every slot accepts a listed option or free text, and the sentence can be edited as plain language. It renders above a generate button in a quick-action panel.

### Props
- `parts`: Array<string | { key: string; label: string; options: string[]; placeholder?: string }>, the sentence as text pieces and slots, e.g. ["Write a ", { key: "tone", ... }, " reply that ", { key: "goal", ... }].
- `values`: Record<string, string> and `onChange(key: string, value: string)`, controlled slot values, pre-filled with sensible defaults by the parent.
- `mode`: "slots" | "freeText" and `onModeChange(mode)`, switching between the sentence with blanks and a plain text box.
- `status`: "editing" | "generating" | "done" | "error".
- `onGenerate(result: { prompt: string; values: Record<string, string> | null })`: compiled string plus structured values (null in free-text mode). The component never runs it itself.
- `defaultsNote?`: string, e.g. "Defaults from this ticket".

### States it must render
- Slots: the sentence reads naturally with each slot visibly editable (underlined, chip-like) and showing its current value.
- Slot open: a small popover with the options and a text field for a custom value.
- Empty slot: shows its placeholder in a muted style; Generate is disabled and the slot is named.
- Free text: the compiled sentence in an editable text box; slots are no longer tracked.
- Generating, done and error: the sentence stays readable and locked while generating; on error, values are kept.

### Interactions
- Clicking a slot opens its options; picking one or typing a custom value and pressing Enter calls `onChange` and closes the popover.
- "Edit as text" switches to free-text mode with the compiled sentence as the starting text; switching back warns that text edits will be lost.
- Generate calls `onGenerate` with the compiled prompt and, in slots mode, the structured values.

### Edge cases
- A custom value is never rejected for not being in `options`; it is shown in the slot exactly as typed.
- A slot value that makes the sentence ungrammatical (e.g. a full sentence typed in): show it as is; the compiled prompt uses it verbatim.
- More than about 4 slots makes the pattern unreadable; render them all but this is a sign the parent should use a form instead.
- Long slot values: the sentence reflows; slots never truncate their value.

## Must avoid
Slots that only accept the options you thought of, with no escape hatch to plain language.

## Engineering constraint
Compile slots to a prompt string but keep the structured values for analytics and re-runs.

## Accessibility
- Each slot is a button whose accessible name is "label: value" and whose popover is a labelled dialog or listbox.
- The full compiled sentence is available as one readable string for screen readers, not only as fragments.

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