{
  "count": 73,
  "prompts": [
    {
      "slug": "starter-prompt-gallery",
      "name": "Starter prompt gallery",
      "family": "Wayfinders",
      "component": "StarterPromptGallery",
      "prompt": "# Build the \"Starter prompt gallery\" pattern: `StarterPromptGallery`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA curated set of example prompts on the empty state, teaching capability by demonstration rather than description.\n\nUse it when: Cold-start surfaces where the input is open-ended and the user has no mental model of scope yet.\n\n## What to build\nA small set of example prompts shown on an empty chat or input, each a real task this product can do with the data it can reach. Picking one fills the input so a person sees the scope by example. It renders in the empty state above or around the composer.\n\n### Props\n- `starters`: Array<{ id: string; verb: string; text: string; uses?: string[] }>, served from config, e.g. { verb: \"Summarize\", text: \"Summarize this ticket and suggest a reply\", uses: [\"Tickets\", \"Billing\"] }.\n- `heading?`: string and `scopeNote?`: string, e.g. a line stating which data the assistant can read here.\n- `groups?`: Array<{ title: string; ids: string[] }>, optional grouping by job.\n- `status`: \"loading\" | \"ready\" | \"hidden\".\n- `onPick(id: string, text: string)`: fills the composer. The component never sends the prompt itself.\n- `onImpression?(ids: string[])`: called once when the cards are shown, so the parent can log and rotate them.\n\n### States it must render\n- Loading: card-shaped placeholders that keep the composer from jumping.\n- Ready: 3 to 6 cards, each with its verb, the full prompt text and the data sources it uses.\n- Grouped: cards under short group titles when `groups` is passed.\n- Picked: the chosen card is marked and the composer holds its text, ready to edit.\n- Hidden: once the conversation has started or the person types, the gallery is gone.\n\n### Interactions\n- Clicking a card calls `onPick`; the parent places the text in the composer with focus at the end so it can be edited before sending.\n- Typing in the composer hides the gallery; clearing the composer before any message brings it back.\n- `onImpression` fires once per display, not on every re-render.\n\n### Edge cases\n- Prompts should name concrete objects and data the product really holds; a starter whose `uses` sources are not connected should be omitted by the parent, and the card shows its sources so a mismatch is visible.\n- Prompt text with placeholders like `[ticket]`: show them as visible blanks rather than sending them literally.\n- More than 6 starters passed in: show 6 and do not paginate the empty state.\n- Very long prompt text: wrap to 3 lines, then truncate with the full text in the accessible name.\n\n## Must avoid\nGeneric filler prompts nobody wants (\"Write a poem\") that teach the wrong ceiling for a serious tool.\n\n## Engineering constraint\nServe from a config, not the model. Log which cards get clicked and rotate on that signal.\n\n## Accessibility\n- Cards are buttons in a labelled list; the accessible name is the full prompt text plus its sources.\n- After picking, focus moves to the composer.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "contextual-nudge",
      "name": "Contextual nudge",
      "family": "Wayfinders",
      "component": "ContextualNudge",
      "prompt": "# Build the \"Contextual nudge\" pattern: `ContextualNudge`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nAn in-place hint that an AI action is available on the object the user is already looking at.\n\nUse it when: When the capability is buried in a menu and discovery is the actual bottleneck.\n\n## What to build\nA small, dismissible hint attached to the object a person is already looking at, offering one AI action that is known to work on that object. It states why it appeared and opens straight into the action. It renders inline next to a record, message, row or empty field.\n\n### Props\n- `eligible`: boolean, decided by the parent from a server-side check. When false the component renders nothing.\n- `reason`: string, the specific trigger in plain words, e.g. \"These two charges match\".\n- `actionLabel`: string, a verb phrase naming the one action, e.g. \"Draft refund reply\".\n- `variant`: \"inline\" | \"placeholder\", a bordered hint beside content, or a quiet placeholder inside an empty input that disappears on typing.\n- `status`: \"idle\" | \"running\" | \"done\" | \"error\", controlled by the parent after the action is taken.\n- `onAccept()`: called when the person takes the action. The component never performs it itself.\n- `onDismiss(scope: \"this\" | \"type\")`: dismiss for this object only, or stop showing this kind of nudge.\n- `shortcut?`: string, a key hint shown beside the label when the action has one.\n\n### States it must render\n- Hidden: `eligible` is false, nothing renders and no space is reserved.\n- Idle: one line with the AI accent, the reason, the action button and a dismiss control.\n- Placeholder: inside an empty field, muted hint text with the shortcut; it vanishes as soon as the person types.\n- Running: the action button shows progress and is disabled; dismiss stays available.\n- Done: the nudge collapses away once the result is shown elsewhere; it does not linger as a badge.\n- Error: a short message that the action failed on this object, with retry.\n\n### Interactions\n- The action button calls `onAccept` and nothing else; it never opens a menu of unrelated actions.\n- Dismiss offers \"Not for this one\" and \"Stop suggesting this\", each calling `onDismiss` with the matching scope.\n- In placeholder variant, the shortcut key calls `onAccept` only while the field is empty and focused.\n\n### Edge cases\n- Several eligible objects on one screen: the parent should pass `eligible` for at most one; the component shows no generic sparkle when it has no specific `reason`.\n- Eligibility changes while visible (e.g. the record is edited): hide immediately rather than let the person open into \"not available here\".\n- An accept that the server then rejects: show the error state on the nudge, not a modal.\n- A previously dismissed object: the parent passes `eligible: false`; the component holds no memory of its own.\n\n## Must avoid\nSprinkling sparkle icons on every surface until the badge means nothing and users tune it out.\n\n## Engineering constraint\nGate on eligibility server-side; a nudge that opens into \"not available here\" is worse than no nudge.\n\n## Accessibility\n- The nudge is a `note` (or `aside`) labelled by its reason, placed after the content it refers to in reading order so it is met in context.\n- Do not move focus to the nudge when it appears; announce its appearance once, politely.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "prompt-template-fill-in-form",
      "name": "Prompt template / fill-in form",
      "family": "Wayfinders",
      "component": "PromptTemplateForm",
      "prompt": "# Build the \"Prompt template / fill-in form\" pattern: `PromptTemplateForm`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA structured form that produces a good prompt, trading expressive freedom for reliable output.\n\nUse it when: Repeated jobs with known variables, and for users who freeze at a blank input.\n\n## What to build\nA short form that fills a versioned prompt template, with a live preview of the exact prompt that will run. It lets a person produce a reliable prompt for a repeated job by choosing a few values instead of writing from scratch. It renders in a side panel or modal beside the place the output will go.\n\n### Props\n- `template`: { id: string; version: number; title: string; body: string; fields: Array<{ key: string; label: string; type: \"select\" | \"text\" | \"segmented\"; options?: string[]; required?: boolean; allowCustom?: boolean }> }, where `body` contains `{key}` placeholders.\n- `values`: Record<string, string> and `onChange(key: string, value: string)`, controlled field values.\n- `fixedText?`: string[], any instructions the template always adds, shown in the preview so nothing is hidden.\n- `meta?`: { editedBy?: string; editedAt?: Date }, shown as the template's provenance.\n- `status`: \"editing\" | \"generating\" | \"done\" | \"error\".\n- `onGenerate(compiled: { templateId: string; version: number; values: Record<string, string>; prompt: string })`: the component never runs the prompt itself.\n- `onSwitchToFreeText?()`: leaves the form for a plain prompt box, carrying the compiled text.\n\n### States it must render\n- Editing: fields on one side, the compiled prompt below or beside them with each filled value highlighted and empty slots visibly marked.\n- Incomplete: required fields empty; Generate is disabled and the missing slots are named.\n- Generating: fields locked, progress on the button, preview still readable.\n- Done: the result is handed to the parent; the form offers \"Change inputs\" to return to editing with values kept.\n- Error: values stay as entered, with a message and retry.\n\n### Interactions\n- Changing any field updates the preview immediately, word for word.\n- Generate calls `onGenerate` with template id, version, structured values and the compiled string.\n- A select field with `allowCustom` accepts typed text as well as listed options.\n- \"Write it myself\" calls `onSwitchToFreeText` with the current compiled prompt.\n\n### Edge cases\n- Templates with more than about 5 fields: show a field count and an estimated time beside Generate so length is visible; designers should split such templates.\n- Every instruction that reaches the model appears in the preview; there is no hidden field or silent override.\n- The template version changes while a person is editing: keep their values, show \"Template updated to v5\", and refresh the preview.\n- A value containing braces or quotes: insert literally, never re-interpreted as a placeholder.\n\n## Must avoid\nForms so long the user would have rather typed a sentence; hidden fields that silently override intent.\n\n## Engineering constraint\nStore templates as versioned prompt objects so you can improve wording without breaking saved runs.\n\n## Accessibility\n- Each field has a visible label; the preview is a labelled region updated with `aria-live=\"polite\"` debounced to pauses in typing.\n- Highlighted values in the preview are distinguished by underline or weight as well as colour.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "follow-up-suggestions",
      "name": "Follow-up suggestions",
      "family": "Wayfinders",
      "component": "FollowUpChips",
      "prompt": "# Build the \"Follow-up suggestions\" pattern: `FollowUpChips`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nPost-response chips that propose the next reasonable move, keeping momentum without new typing.\n\nUse it when: Multi-turn work where the next step is predictable and cheap to offer.\n\n## What to build\nA short row of next-step chips shown under an AI response, generated from what the response said. Each chip states its cost or duration, so a cheap reply and an expensive run never look the same. It renders at the end of a chat or answer message.\n\n### Props\n- `suggestions`: Array<{ id: string; text: string; kind: \"reply\" | \"run\"; costLabel?: string }>, at most three, e.g. { text: \"Research the price change by cohort\", kind: \"run\", costLabel: \"Deep research, about 2 min\" }.\n- `label?`: string, a heading such as \"Follow-ups from this answer\".\n- `status`: \"loading\" | \"ready\" | \"used\" | \"hidden\", controlled by the parent.\n- `usedId?`: string, the chip that was chosen, shown as selected in the used state.\n- `onPick(id: string)`: called when a chip is chosen. The component never sends the message or starts the run itself.\n- `onEdit?(text: string)`: puts the chip text into the composer for editing instead of sending.\n\n### States it must render\n- Loading: up to three slim placeholders after the response, so text does not jump when chips arrive.\n- Ready: up to three chips; quick replies show \"Instant\" or nothing, runs show their cost label and a distinct marker.\n- Used: the chosen chip stays visible as selected and the others are disabled, so the thread shows which path was taken.\n- Hidden: nothing renders, used when the response has no sensible next step.\n\n### Interactions\n- Clicking a quick-reply chip calls `onPick` immediately.\n- Clicking a run chip shows its cost on the chip and requires a second confirm (or the parent's own gate) before `onPick` fires.\n- An edit affordance on each chip calls `onEdit` with its text, so a person can adjust before sending.\n\n### Edge cases\n- More than three suggestions passed in: render only the first three.\n- Near-duplicate suggestions: the component cannot judge meaning, but it must drop exact or case-insensitive duplicates.\n- A run chip with no `costLabel`: show \"Cost unknown\" rather than presenting it like a free reply.\n- A new response arrives: the previous message's chips switch to used or hidden so only the latest set is actionable.\n\n## Must avoid\nSuggesting three variations of the same question, or chips that fire expensive runs with no cost signal.\n\n## Engineering constraint\nGenerate follow-ups from the response, not the prompt, and cap at three to protect scan cost.\n\n## Accessibility\n- Chips are a labelled group of buttons; the cost label is part of each button's accessible name.\n- Announce once when suggestions become available; do not move focus away from the response.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "capability-disclosure",
      "name": "Capability disclosure",
      "family": "Wayfinders",
      "component": "CapabilityStatement",
      "prompt": "# Build the \"Capability disclosure\" pattern: `CapabilityStatement`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA plain statement of what the agent can do, what it cannot, and what it will never do without asking.\n\nUse it when: First run of any agent with side effects, and any surface where users must calibrate trust fast.\n\n## What to build\nA panel that tells a person, in their own terms, what the agent does on its own, what it asks about first, and what it never does. It is built from the agent's registered tools, so it always matches what is switched on, and it can show exactly what the agent would do for a sample request. It renders on an agent's setup screen, settings page or first-run sheet.\n\n### Props\n- `capabilities`: Array<{ id: string; tier: \"auto\" | \"ask\" | \"never\"; text: string; toolId?: string; limit?: string }>, one row per behaviour, derived from the registered tool set by the parent, e.g. { tier: \"ask\", text: \"Refund a customer\", toolId: \"billing.refund\", limit: \"Up to $2,000 per ticket\" }.\n- `tierLabels`: Record<\"auto\" | \"ask\" | \"never\", { title: string; note: string }>, e.g. \"Does on its own\" / \"Logged on the record\".\n- `source`: { toolCount: number; generatedAt: Date; changedBy?: string }, shown as a provenance line so readers know the list came from the live tool set.\n- `samples?`: Array<{ id: string; request: string; steps: Array<{ text: string; tier: \"auto\" | \"ask\" | \"never\" }> }>, example requests and the ordered steps the agent would take for each.\n- `showToolIds?`: boolean, reveals the tool identifier beside each row for technical readers (default false).\n- `status`: \"loading\" | \"ready\" | \"error\" | \"stale\", controlled by the parent.\n- `onEnable?()` and `onEditPermissions?(capabilityId: string)`: callbacks for the primary action and per-row changes. The component never changes permissions itself.\n\n### States it must render\n- Loading: three tier groups with skeleton rows, so the layout does not jump when the list arrives.\n- Ready: three groups in fixed order (auto, ask, never), each row a plain sentence about behaviour with its limit, and a provenance line such as \"Built from the 9 tools enabled here\".\n- Sample selected: the chosen request expands into a numbered list of steps, each tagged with its tier, so a person sees where the agent would stop and wait.\n- Empty tier: a group with no rows still renders with a line such as \"Nothing in this group\", so its absence is visible rather than ambiguous.\n- Stale: the tool set changed since the list was generated; a notice says so and offers refresh instead of showing an out-of-date list as current.\n- Error: the list could not be built; say so and do not render a generic placeholder list.\n\n### Interactions\n- Selecting a sample request shows its steps; selecting it again collapses them. Only one sample is open at a time.\n- Each row in the ask and auto groups has an optional \"Change\" control that calls `onEditPermissions(id)`.\n- The primary action calls `onEnable`; it is disabled while loading, in error or stale.\n\n### Edge cases\n- A capability with no backing tool (the agent cannot do it at all) belongs in the never group and its note should say why, e.g. \"No tool connected\".\n- Rows must describe behaviour, never risk transfer: reject or flag text supplied as a disclaimer (\"You are responsible for…\") by keeping the row format to a verb-led action and limit.\n- A sample step whose tier is never: render it as a stop, with the steps after it shown as not taken.\n- Long lists: keep all never rows visible; collapse auto rows beyond 6 behind \"Show all\".\n\n## Must avoid\nLegal-flavoured disclaimers that dump risk on the user instead of describing behaviour in their terms.\n\n## Engineering constraint\nGenerate the list from the actual registered tool set so it cannot drift from what shipped.\n\n## Accessibility\n- Each tier group is a list with a heading; the tier is stated in text in every sample step, not only by colour or icon.\n- Sample requests are buttons with `aria-expanded`, and the step list they control is linked with `aria-controls`.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "example-gallery",
      "name": "Example gallery",
      "family": "Wayfinders",
      "component": "ExampleGallery",
      "prompt": "# Build the \"Example gallery\" pattern: `ExampleGallery`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nFinished outputs shown with the prompt and settings that produced them, teaching by worked example.\n\nUse it when: Generative tools where quality depends on prompt craft the user has not learned yet.\n\n## What to build\nA grid of finished outputs, each shown with the exact prompt and settings that produced it and an honest note on how many tries it took. A person can open any example to read its recipe and reuse it in one click, which reproduces it exactly. It renders on a new-item screen or an explore page.\n\n### Props\n- `examples`: Array<{ id: string; title: string; preview: ReactNode; prompt: string; params: Record<string, string | number>; attempts: number; edited: boolean; dataNote?: string }>, every example carries its stored prompt and parameters.\n- `selectedId?`: string and `onSelect(id: string | null)`, controlled selection of the example whose recipe is open.\n- `onUse(example: { prompt: string; params: Record<string, string | number> })`: loads the stored recipe into the editor. The component never runs it itself.\n- `onCopyPrompt?(id: string)`: copies only the prompt text.\n- `status`: \"loading\" | \"ready\" | \"empty\" | \"error\".\n- `columns?`: 1 | 2 | 3, the grid width hint; the component drops to 1 column in narrow containers.\n\n### States it must render\n- Loading: placeholder cards with the same shape as real ones.\n- Ready: cards with preview, title and a provenance tag such as \"First try, unedited\" or \"Second try\".\n- Recipe open: the selected card shows the full prompt, each parameter as a label and value, and Use this and Copy prompt actions.\n- Empty: a short line that no examples are available, without placeholder art.\n- Error: the gallery failed to load, with retry.\n\n### Interactions\n- Selecting a card opens its recipe; selecting it again or pressing Escape closes it.\n- Use this calls `onUse` with the stored prompt and parameters verbatim, never a paraphrase.\n- Copy prompt calls `onCopyPrompt` and confirms briefly next to the button.\n\n### Edge cases\n- An example missing its stored prompt or parameters must not render a Use this button; show \"Recipe not available\" so the gallery never promises a reproduction it cannot give.\n- An example that took several attempts or was edited: say so on the card face, not only inside the recipe.\n- An example made from sample data: show `dataNote` (e.g. \"Sample workspace data\") so a person knows their own results will differ.\n- A parameter the current product no longer supports: mark it in the recipe and still pass it in `onUse` for the parent to handle.\n\n## Must avoid\nA gallery of cherry-picked hero results that sets an expectation the user's own runs never meet.\n\n## Engineering constraint\nStore prompt and parameters with each sample so \"use this\" reproduces it exactly.\n\n## Accessibility\n- Each card is a button with `aria-expanded` labelled by its title and provenance; previews need a text alternative from `title`.\n- When a recipe opens, keep focus on the card and make the recipe the next element in tab order.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "randomize",
      "name": "Randomize",
      "family": "Wayfinders",
      "component": "RandomizeControl",
      "prompt": "# Build the \"Randomize\" pattern: `RandomizeControl`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA surprise-me action that produces a valid result with no input, lowering the cost of the first try.\n\nUse it when: Exploratory creative tools, and any surface where a blank input is the drop-off point.\n\n## What to build\nA \"Surprise me\" control that produces a valid result with no input, then shows the seed and the full prompt it chose so a lucky result can be reproduced and steered. It keeps a short history of rolls in the session. It renders beside an empty input or generation canvas.\n\n### Props\n- `dimensions`: Array<{ key: string; label: string; options: string[]; locked?: string }>, the things a roll can vary (palette, layout, length); a locked value is held fixed across rolls.\n- `current?`: { seed: string; picks: Record<string, string>; prompt: string; preview?: ReactNode }, the latest roll.\n- `history`: Array<{ seed: string; picks: Record<string, string>; preview?: ReactNode }>, rolls from this session, newest first.\n- `status`: \"empty\" | \"rolling\" | \"ready\" | \"error\".\n- `onRoll(locks: Record<string, string>)`: requests a new roll with current locks. The component never generates itself.\n- `onLock(key: string, value: string | null)`: pins or releases one dimension.\n- `onRestore(seed: string)` and `onUse(seed: string)`: return to an earlier roll, or commit the current one.\n\n### States it must render\n- Empty: a short invitation and the Surprise me button; the prompt area says nothing has been picked yet.\n- Rolling: button busy, previous result kept visible until the new one arrives.\n- Ready: the result, its seed with a copy control, each picked value with a lock toggle, and the full prompt that was used.\n- History: earlier rolls as small thumbnails with their seeds; the current one is marked.\n- Error: the roll failed; the previous result and history stay.\n\n### Interactions\n- Surprise me calls `onRoll` with the current locks, so a person can keep the palette they liked and vary the rest.\n- Locking a pick calls `onLock`; locked picks show a lock state and are not changed by the next roll.\n- Selecting a history item calls `onRestore(seed)`; Copy seed copies it and confirms briefly.\n- Use this calls `onUse` with the current seed.\n\n### Edge cases\n- Every result, including the first, must show its seed and prompt; no roll is ever unrecoverable.\n- All dimensions locked: the button changes to \"Re-run this seed\" since nothing is left to vary.\n- History beyond about 12 items: keep the newest and say how many older rolls were dropped.\n- Restoring a seed whose options no longer exist: show the stored picks and flag the missing ones.\n\n## Must avoid\nRandomness with no path back — the user gets something interesting and cannot steer toward it again.\n\n## Engineering constraint\nSeed the randomiser and surface the seed so a lucky result is reproducible.\n\n## Accessibility\n- Lock toggles are buttons with `aria-pressed` and labels like \"Lock palette: dusk\".\n- Announce a new roll by its key picks, not by repeating the full prompt.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "multimodal-drop-zone",
      "name": "Multimodal drop zone",
      "family": "Inputs",
      "component": "AttachmentDropZone",
      "prompt": "# Build the \"Multimodal drop zone\" pattern: `AttachmentDropZone`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nOne input that accepts text, files, images, audio and screen captures as equal citizens.\n\nUse it when: Whenever the user's evidence lives outside the app and describing it is lossy.\n\n## What to build\nOne input area that accepts text plus files, images, audio and screenshots, and shows each attachment with its own upload and parse status. A person can see what will be sent, remove anything, and cancel slow uploads before asking. It renders as part of a chat composer or request form.\n\n### Props\n- `attachments`: Array<{ id: string; name: string; kind: \"image\" | \"pdf\" | \"audio\" | \"table\" | \"text\" | \"other\"; size: number; status: \"uploading\" | \"parsing\" | \"ready\" | \"failed\" | \"unsupported\"; progress?: number; summary?: string; error?: string; thumbnail?: string }>, controlled by the parent.\n- `accept`: Array<{ kind: string; mimeTypes: string[]; maxBytes: number }>, what can be added and size limits, shown as a hint.\n- `onAdd(files: File[])`: called on drop, paste or picker selection. The component never uploads or parses itself.\n- `onRemove(id: string)`, `onCancel(id: string)` and `onRetry(id: string)`: per-attachment callbacks.\n- `text`: string and `onTextChange(text: string)`, the message typed alongside.\n- `onSend()`: enabled only when nothing is still uploading or parsing, or when the parent allows sending without pending items.\n- `maxAttachments?`: number.\n\n### States it must render\n- Empty: the input with a hint listing accepted kinds (\"Images, PDF, audio, CSV\") and an add button.\n- Drag over: the whole area is outlined with \"Drop to attach\".\n- Uploading: a chip per file with progress and Cancel.\n- Parsing: the chip says what is being read, e.g. \"Reading 3 pages\".\n- Ready: each chip shows a thumbnail or icon and what was understood, e.g. \"2 invoices found\" or \"0:42 audio, transcribed\".\n- Failed or unsupported: the chip stays in place with the reason and Retry or Remove; it is never silently dropped.\n\n### Interactions\n- Drop, paste or the add button calls `onAdd` with the files; rejected types still appear as unsupported chips with the reason.\n- Each chip has Remove (and Cancel while uploading) calling the matching callback.\n- Send is disabled while any attachment is uploading or parsing, with a note naming how many are pending.\n\n### Edge cases\n- A file that uploads but cannot be parsed: status failed with the parse error, so the person knows the model will not see it.\n- A file over `maxBytes`: unsupported immediately, with the limit stated, before any upload starts.\n- Exceeding `maxAttachments`: extra files are listed as not added, with the limit.\n- Duplicate drop of the same file: show it once and say it was already attached.\n\n## Must avoid\nAccepting an upload then silently ignoring it, or failing at parse time with no per-file state.\n\n## Engineering constraint\nShow per-attachment parse status; large files need progressive upload with a cancel affordance.\n\n## Accessibility\n- The drop zone is not the only way in: a real file input button is always present and labelled.\n- Each status change per attachment is announced politely with the file name, e.g. \"invoice.pdf ready, 2 invoices found\".\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "reference-token-layering",
      "name": "Reference token layering",
      "family": "Inputs",
      "component": "ReferenceTokenComposer",
      "prompt": "# Build the \"Reference token layering\" pattern: `ReferenceTokenComposer`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nTyped @-mentions that pin exact objects — files, people, tickets, tables — into the prompt as structured references.\n\nUse it when: Products with a real object graph, where precision beats prose.\n\n## What to build\nA prompt composer where typing @ opens a typed picker of real objects (files, records, people, tickets), and each choice becomes a token pinned to a stable ID. Before sending, it lists exactly which records the agent will read. It renders as the main input of a chat or command surface.\n\n### Props\n- `value`: Array<string | { token: { id: string; type: string; label: string; meta?: string } }> and `onChange(value)`, the composer content as text and resolved tokens.\n- `categories`: Array<{ type: string; label: string }>, the object types offered in the picker.\n- `results`: Array<{ id: string; type: string; label: string; meta?: string }> and `onSearch(query: string, type?: string)`, picker results supplied by the parent.\n- `resolution?`: Array<{ id: string; status: \"resolved\" | \"missing\" | \"noAccess\" }>, the parent's check of each token at send time.\n- `status`: \"editing\" | \"picking\" | \"sending\" | \"error\".\n- `onSend(payload: { text: string; refs: Array<{ id: string; type: string }> })`: sends text plus stable IDs. The component never sends names to be searched again.\n\n### States it must render\n- Editing: text with inline tokens showing type icon and label, each removable.\n- Picking: a popover below the caret with category tabs and results, each showing type and disambiguating meta (path, owner, date).\n- Resolved set: a line under the composer such as \"Will read exactly 2 records\", listing each token.\n- Unresolved: a token whose record is missing or not accessible is marked with the reason, and Send is blocked until it is removed or replaced.\n- Sending: tokens locked; error state keeps the content.\n\n### Interactions\n- Typing @ opens the picker and calls `onSearch` as the query changes; Tab or arrow keys switch category.\n- Enter or click inserts the highlighted result as a token; Escape closes the picker and leaves the typed @ text as plain text.\n- Backspace next to a token selects it first, then removes it on a second press.\n- Send calls `onSend` with token IDs, not their labels.\n\n### Edge cases\n- Two records with the same label: the picker must show meta to tell them apart, and the token keeps the chosen ID.\n- Text that looks like a name but was not picked stays plain text and is never treated as a reference.\n- A token whose record was renamed: keep the ID, show the new label from `resolution` if provided.\n- Pasted text containing tokens from elsewhere: paste as plain text unless the IDs can be resolved.\n\n## Must avoid\nTokens that look precise but resolve to a fuzzy search behind the scenes, so results silently miss the target.\n\n## Engineering constraint\nResolve tokens to stable IDs at send time and render the resolved set back to the user.\n\n## Accessibility\n- The picker follows the combobox pattern (`aria-expanded`, `aria-activedescendant`); each token's accessible name includes its type, e.g. \"Account: Acme renewal\".\n- Announce the resolved count before send when it changes.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "madlib-prompt",
      "name": "Madlib prompt",
      "family": "Inputs",
      "component": "MadlibPrompt",
      "prompt": "# Build the \"Madlib prompt\" pattern: `MadlibPrompt`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA sentence with editable slots, so the grammar of a good request is visible and the user just fills blanks.\n\nUse it when: Narrow generative tasks with two or three real variables and a novice audience.\n\n## What to build\nA 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.\n\n### Props\n- `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\", ... }].\n- `values`: Record<string, string> and `onChange(key: string, value: string)`, controlled slot values, pre-filled with sensible defaults by the parent.\n- `mode`: \"slots\" | \"freeText\" and `onModeChange(mode)`, switching between the sentence with blanks and a plain text box.\n- `status`: \"editing\" | \"generating\" | \"done\" | \"error\".\n- `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.\n- `defaultsNote?`: string, e.g. \"Defaults from this ticket\".\n\n### States it must render\n- Slots: the sentence reads naturally with each slot visibly editable (underlined, chip-like) and showing its current value.\n- Slot open: a small popover with the options and a text field for a custom value.\n- Empty slot: shows its placeholder in a muted style; Generate is disabled and the slot is named.\n- Free text: the compiled sentence in an editable text box; slots are no longer tracked.\n- Generating, done and error: the sentence stays readable and locked while generating; on error, values are kept.\n\n### Interactions\n- Clicking a slot opens its options; picking one or typing a custom value and pressing Enter calls `onChange` and closes the popover.\n- \"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.\n- Generate calls `onGenerate` with the compiled prompt and, in slots mode, the structured values.\n\n### Edge cases\n- A custom value is never rejected for not being in `options`; it is shown in the slot exactly as typed.\n- 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.\n- More than about 4 slots makes the pattern unreadable; render them all but this is a sign the parent should use a form instead.\n- Long slot values: the sentence reflows; slots never truncate their value.\n\n## Must avoid\nSlots that only accept the options you thought of, with no escape hatch to plain language.\n\n## Engineering constraint\nCompile slots to a prompt string but keep the structured values for analytics and re-runs.\n\n## Accessibility\n- Each slot is a button whose accessible name is \"label: value\" and whose popover is a labelled dialog or listbox.\n- The full compiled sentence is available as one readable string for screen readers, not only as fragments.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "inline-action-menu",
      "name": "Inline action menu",
      "family": "Inputs",
      "component": "SelectionActionMenu",
      "prompt": "# Build the \"Inline action menu\" pattern: `SelectionActionMenu`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nSelect content, get AI verbs scoped to that selection — rewrite, explain, extend, translate.\n\nUse it when: Document, code and canvas editors where the object of the action is whatever is highlighted.\n\n## What to build\nA small menu that appears next to a selection in an editor and offers AI actions that fit what was selected. It sends the selection plus its surrounding paragraph and shows the proposed change in place for accept or discard. It renders as a floating toolbar anchored to the selection.\n\n### Props\n- `selection`: { text: string; kind: \"word\" | \"phrase\" | \"sentence\" | \"paragraph\" | \"multi\" | \"code\"; context: string; rect: DOMRect }, where `context` is the surrounding paragraph or block.\n- `actions`: Array<{ id: string; label: string; kinds: Array<\"word\" | \"phrase\" | \"sentence\" | \"paragraph\" | \"multi\" | \"code\"> }>, each action lists the selection kinds it applies to.\n- `allowCustom?`: boolean, shows a free-text instruction field scoped to the selection.\n- `status`: \"menu\" | \"running\" | \"preview\" | \"error\".\n- `preview?`: { original: string; proposed: string }, the suggested replacement.\n- `onAction(id: string, payload: { text: string; context: string; instruction?: string })`: the component never edits the document itself.\n- `onAccept()`, `onDiscard()` and `onClose()`: callbacks for the preview and menu.\n\n### States it must render\n- Menu: only actions whose `kinds` include the selection kind, with a label naming the selection (\"1 word\", \"2 sentences\").\n- Running: the selection stays highlighted with an AI accent and a busy label; the menu offers Cancel.\n- Preview: the original struck through or dimmed and the proposed text beside it, with Accept and Discard.\n- Error: a short message in the menu with retry; the document is untouched.\n\n### Interactions\n- Choosing an action calls `onAction` with the selection text and its context.\n- Accept calls `onAccept` and closes; Discard restores the view with the original intact.\n- Escape closes the menu (or discards a preview) and returns focus to the editor with the selection kept.\n- Changing the selection while the menu is open re-filters the actions.\n\n### Edge cases\n- A single word: offer actions like define, synonyms or translate; summarise and extend are hidden because their `kinds` exclude \"word\".\n- A selection across several paragraphs or blocks: kind is \"multi\" and the preview shows each block's change.\n- No action matches the kind: show only the custom instruction field, or nothing if `allowCustom` is false.\n- The menu near the viewport edge: flip above or below the selection so it never covers the selected text.\n\n## Must avoid\nA verb list that ignores the selection type, offering \"summarise\" on a single word.\n\n## Engineering constraint\nPass the selection plus surrounding context; users expect awareness of the paragraph, not just the span.\n\n## Accessibility\n- The menu is a `menu` with `menuitem`s, reachable by a keyboard shortcut from the selection, with arrow-key navigation.\n- In preview, announce \"Suggested change ready\" and put focus on Accept.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "ghost-completion",
      "name": "Ghost completion",
      "family": "Inputs",
      "component": "GhostCompletionInput",
      "prompt": "# Build the \"Ghost completion\" pattern: `GhostCompletionInput`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nGreyed inline prediction the user accepts with a key, keeping the model subordinate to the typist.\n\nUse it when: High-frequency typing surfaces where interruption cost is higher than error cost.\n\n## What to build\nA text input that shows a model's predicted continuation as dimmed text after the cursor. The typist stays in control: one dedicated key accepts it, and any other typing makes it disappear. It renders as a reply box, editor line or composer.\n\n### Props\n- `value`: string and `onChange(value: string)`, the controlled text the person has typed.\n- `suggestion?`: string, the predicted continuation for the current cursor position, supplied by the parent after its debounce.\n- `suggestionFor?`: string, the exact `value` the suggestion was computed for; a suggestion for any other value is ignored.\n- `acceptKey?`: \"Tab\" | \"ArrowRight\" (default \"Tab\") and `acceptWordKey?`: string, accept the whole suggestion or just the next word.\n- `onAccept(text: string, mode: \"all\" | \"word\")`, `onDismiss()` and `onRequest(value: string, cursor: number)`: the parent debounces, cancels in-flight requests and fetches.\n- `latencyMs?`: number, optionally shown as a quiet \"Suggested in 138 ms\" hint.\n- `enabled`: boolean, lets a person turn suggestions off.\n\n### States it must render\n- Typing: plain text, no ghost; nothing appears while keys are being pressed.\n- Suggesting: dimmed text after the cursor and a small key hint (\"Tab to accept, Esc to dismiss\").\n- Partially accepted: the accepted words become normal text and the rest stays ghosted.\n- Dismissed: ghost removed until the next pause.\n- Off: `enabled` false, behaves as a normal input.\n\n### Interactions\n- The accept key inserts the ghost text and calls `onAccept`; it does so only while a ghost is visible. Without a ghost, Tab moves focus as usual.\n- Any character key that does not match the next ghost character clears the ghost and is typed normally; a matching character consumes it.\n- Escape, moving the cursor, clicking elsewhere or selecting text clears the ghost and calls `onDismiss`.\n- `onRequest` is called after typing pauses; the component never requests on every keystroke.\n\n### Edge cases\n- A suggestion arriving after the person kept typing (`suggestionFor` no longer matches `value`): drop it silently, never paste stale text.\n- Enter submits the message and never accepts a ghost.\n- Cursor in the middle of existing text: show the ghost only if the parent supplies one for that position, without overlapping following text.\n- IME composition in progress: hide the ghost until composition ends.\n\n## Must avoid\nAggressive completions that fight the cursor, or accepting on a key the user presses for something else.\n\n## Engineering constraint\nDebounce on keystroke and cancel in-flight requests; latency budget under ~200ms or it reads as lag.\n\n## Accessibility\n- The ghost text is not part of the input value; expose it with `aria-describedby` hint text such as \"Suggestion available, Tab to accept\".\n- Announce acceptance briefly; never announce each new suggestion.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "prompt-enhancer",
      "name": "Prompt enhancer",
      "family": "Inputs",
      "component": "PromptEnhancer",
      "prompt": "# Build the \"Prompt enhancer\" pattern: `PromptEnhancer`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nThe system rewrites a thin prompt into a fuller one and shows the rewrite before running it.\n\nUse it when: Products whose users under-specify, especially image, video and research generation.\n\n## What to build\nA control that rewrites a short prompt into a fuller one and shows the rewrite as editable text before anything runs. It lists what was added and keeps the original one click away, so a person chooses which version to run. It renders under or inside a prompt composer.\n\n### Props\n- `original`: string, the prompt as the person typed it.\n- `enhanced?`: string and `onEnhancedChange(text: string)`, the suggested rewrite, editable and controlled.\n- `additions?`: Array<{ label: string; text: string }>, what the rewrite added, e.g. { label: \"Timeframe\", text: \"Q3\" }.\n- `status`: \"idle\" | \"enhancing\" | \"review\" | \"error\".\n- `onEnhance()`: asks for a rewrite. The component never rewrites on its own or before the person asks.\n- `onRun(prompt: string, which: \"original\" | \"enhanced\")`: runs the chosen version. The component never runs the prompt itself.\n- `onDiscard()`: closes the rewrite and returns to the original.\n\n### States it must render\n- Idle: the composer with an Enhance button beside Run as typed.\n- Enhancing: a busy indicator on Enhance; the original stays visible and editable.\n- Review: the rewrite in an editable text area marked as AI-written and \"Not run yet\", the original text shown inline (struck through where replaced) and a list of additions as chips.\n- Edited: once the person changes the rewrite, its marker changes to \"Edited by you\".\n- Error: enhancement failed; the original is untouched and can still run.\n\n### Interactions\n- Run rewrite calls `onRun(enhanced, \"enhanced\")`; Keep mine calls `onRun(original, \"original\")`; both are equal-weight buttons.\n- Selecting an addition chip highlights its words in the rewrite; a remove control on the chip deletes that addition from the text.\n- Discard calls `onDiscard` and restores the composer to the original exactly.\n\n### Edge cases\n- The rewrite changes meaning (adds a constraint the person did not ask for): additions must list every change so nothing is silent.\n- The person edits the original while a rewrite is pending: mark the rewrite as out of date and offer to enhance again.\n- An empty or one-word prompt: Enhance may ask a clarifying question instead of guessing; render it as a question with an answer field.\n- After a run, the result should be labelled with which version produced it; expose `which` in `onRun` for that.\n\n## Must avoid\nSilently rewriting intent, so the user cannot tell whether the miss was theirs or the enhancer's.\n\n## Engineering constraint\nShow the enhanced prompt as editable text and keep the original for comparison and retry.\n\n## Accessibility\n- The rewrite text area is labelled \"Suggested rewrite, not run yet\"; announce when it is ready.\n- Struck-through original text is also exposed as text (\"replaced: …\") so the change is not visual only.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "parameter-panel",
      "name": "Parameter panel",
      "family": "Tuners",
      "component": "ParameterPanel",
      "prompt": "# Build the \"Parameter panel\" pattern: `ParameterPanel`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nExposed knobs — length, tone, temperature, aspect, effort — that shift output without a new prompt.\n\nUse it when: Creative and analytical tools where users iterate toward a target they recognise on sight.\n\n## What to build\nA panel of a few plainly labelled controls (length, tone, aspect, detail) that change an output without rewriting the prompt. Each control has a safe range, shows when it differs from the person's defaults, and can be reset. It renders beside or under a generated result.\n\n### Props\n- `params`: Array<{ key: string; label: string; type: \"segmented\" | \"slider\" | \"toggle\"; options?: string[]; min?: number; max?: number; step?: number; endLabels?: [string, string]; description?: string }>, e.g. a slider labelled \"More variable\" to \"More stable\".\n- `values`: Record<string, string | number | boolean> and `onChange(key: string, value: string | number | boolean)`, controlled values.\n- `defaults`: Record<string, string | number | boolean>, the person's saved defaults, and `onSaveDefaults(values)`.\n- `promptText?`: string, shown as \"Same prompt\" so a person sees the prompt is unchanged.\n- `status`: \"idle\" | \"applying\" | \"error\".\n- `onReset(key?: string)`: reset one control or all to defaults. The component never regenerates the output itself.\n\n### States it must render\n- Default: every control at the saved default; a line summarises them, e.g. \"Standard, warm, timeline on\".\n- Changed: each control that differs from default shows a \"changed\" marker and its own reset; a Reset all link appears.\n- Applying: the result area is marked as updating; controls stay usable and the latest value wins.\n- Error: the change could not be applied; values revert to the last applied set with a message.\n\n### Interactions\n- Changing a control calls `onChange` once per committed value (on release for sliders, on click for segments).\n- Reset on a control calls `onReset(key)`; Reset all calls `onReset()`.\n- \"Save as my defaults\" calls `onSaveDefaults` with current values and updates the summary line.\n\n### Edge cases\n- Raw model settings (temperature, top_p) must not be passed as labels; every slider needs `endLabels` in plain words, and the component refuses to render a slider without them.\n- Values outside the documented range coming from saved defaults: clamp and show that the value was adjusted.\n- Rapid changes while applying: only the last value is applied; earlier ones are dropped.\n- A parameter that does not apply to the current output: disabled with the reason.\n\n## Must avoid\nSurfacing raw model internals (top_p) that no user can reason about or predict.\n\n## Engineering constraint\nPersist per-user defaults; every param needs a documented safe range and a visible reset.\n\n## Accessibility\n- Sliders expose `aria-valuetext` using the plain end labels (\"Mostly stable\"), not just a number.\n- The changed marker is text, and a reset announces which control returned to default.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "result-filter-and-facets",
      "name": "Result filter and facets",
      "family": "Tuners",
      "component": "ResultFacets",
      "prompt": "# Build the \"Result filter and facets\" pattern: `ResultFacets`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nPost-generation filters that narrow a set of candidates rather than regenerating it.\n\nUse it when: Retrieval, search and multi-candidate generation where the good answer is already in the set.\n\n## What to build\nA set of filters that narrows a list of results the agent already returned, without running it again. Each facet option shows how many results it keeps, and clearing filters brings back the full set instantly. It renders above or beside a result list or table.\n\n### Props\n- `facets`: Array<{ key: string; label: string; options: Array<{ value: string; label: string }>; multi?: boolean }>, e.g. a \"Renews\" facet with months.\n- `items`: Array<{ id: string; facets: Record<string, string | string[]> }>, the full returned set; the component counts and filters over it in memory.\n- `selected`: Record<string, string[]> and `onChange(selected: Record<string, string[]>)`, controlled filter state.\n- `onFiltered?(ids: string[])`: called with the visible ids whenever the selection changes, so the parent renders the list.\n- `runInfo?`: string, e.g. \"Ran once, 38 credits\", shown so a person sees filtering costs nothing more.\n- `onRefine?()`: an explicit, separate action for a new run when the answer is not in the set. The component never triggers a run from a filter.\n\n### States it must render\n- Unfiltered: all facets with counts per option and \"Showing 12 of 12\".\n- Filtered: active options pressed, \"Showing 4 of 12\", active filters as removable chips, and Clear filters.\n- No matches: \"No results match these filters\" with the filters that caused it and Clear filters; the full set is still held.\n- Zero-count option: shown disabled with \"0\" rather than hidden, so the person knows the value exists.\n\n### Interactions\n- Toggling an option recomputes counts and visible ids instantly from `items`, then calls `onChange` and `onFiltered`.\n- Within a facet, options combine with OR; across facets, with AND. Counts reflect the other active facets.\n- Removing a chip or pressing Clear filters restores the previous view without any network call.\n- \"Search again\" (if `onRefine` is provided) is visually separate and labelled with its cost.\n\n### Edge cases\n- An item with multiple values for a facet (e.g. several risk signals): it counts under each value.\n- An item missing a facet value: grouped under \"Not set\" so it is never silently excluded.\n- The parent replaces `items` with a new run: keep selections whose options still exist and drop the rest with a notice.\n- Large sets (thousands of items): counting must stay fast; memoise per selection.\n\n## Must avoid\nFilters that trigger a fresh expensive run and throw away results the user was still comparing.\n\n## Engineering constraint\nFilter client-side over the returned candidate set where possible; keep the unfiltered set in memory.\n\n## Accessibility\n- Each facet is a group with a legend; options are checkboxes or pressed buttons whose names include the count.\n- Announce the result count after each change, e.g. \"Showing 4 of 12\".\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "style-and-voice-presets",
      "name": "Style and voice presets",
      "family": "Tuners",
      "component": "PresetPicker",
      "prompt": "# Build the \"Style and voice presets\" pattern: `PresetPicker`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nNamed, previewable presets that bundle many parameters into one recognisable choice.\n\nUse it when: When output quality depends on combinations of settings users cannot intuit individually.\n\n## What to build\nA grid of named, versioned presets (visual styles, voices, tones) that each bundle several settings into one choice. Each preset shows a preview of its effect and the settings it applies, so a person can pick one without spending a run to find out what the name means. It renders in a generation settings panel or composer sidebar.\n\n### Props\n- `presets`: Array<{ id: string; name: string; version: string; description: string; thumbnailUrl?: string; sampleUrl?: string; settings: Array<{ label: string; value: string }>; tag?: string }>, shipped as data.\n- `selectedId`: string and `onSelect(id: string)`, controlled selection.\n- `previewMode`: \"none\" | \"sample\" | \"live\", whether previews are static samples or a cheap render of the person's own content.\n- `previews?`: Record<string, { status: \"loading\" | \"ready\" | \"error\"; url?: string }>, low-cost previews of the person's content per preset, supplied by the parent.\n- `onRequestPreview?(id: string)`: asks the parent for a free or low-cost preview of that preset.\n- `runCost?`: string, the cost of a full run with the selected preset, e.g. \"3 credits, about 20 s\".\n- `onApply(id: string, version: string)`: commits the choice; the version is passed so the saved artifact can be reproduced later. The component never generates anything itself.\n- `savedPresetIds?`: string[], marks the person's own saved presets so they can be grouped separately.\n\n### States it must render\n- Default: preset tiles with name, version badge, one-line description and thumbnail; the selected tile is pressed and its settings list reads \"Ink line v1 sets 5 things at once\" with each setting as a chip.\n- Previewing: the person's own content rendered in the selected preset, clearly labelled \"Preview, free, low-res\" so it is not mistaken for the final output.\n- Preview loading: a placeholder at the final aspect ratio with a quiet indicator; selection stays responsive.\n- Preview unavailable: the static sample is shown with \"Example output, not your content\".\n- Applied: the chosen preset and version are shown as a summary line, e.g. \"Generated with Ink line v1\".\n\n### Interactions\n- Clicking a tile selects it, updates the settings list and calls `onRequestPreview` when `previewMode` is \"live\".\n- Arrow keys move between tiles; Enter or Space selects.\n- Apply calls `onApply(id, version)` and shows `runCost` on the button before it is pressed.\n- Expanding \"What this sets\" on any tile shows its settings without selecting it.\n\n### Edge cases\n- A preset updated to a new version after an artifact was made with an older one: show \"Made with v2, current is v3\" rather than silently relabelling.\n- A retired preset referenced by a saved artifact: keep its name and settings readable, and mark it \"Retired\".\n- Presets with no thumbnail: show the settings chips prominently instead of an empty box.\n- Many presets (more than 12): group into brand defaults and saved presets, keeping the selected one visible.\n\n## Must avoid\nPreset names that mean nothing without trying them, and no preview of the effect before spending a run.\n\n## Engineering constraint\nShip presets as data with thumbnails; version them so a saved artifact can be reproduced later.\n\n## Accessibility\n- The grid is a `radiogroup` labelled \"Style preset\"; each tile is a `radio` whose accessible name includes name, version and description.\n- Announce when a preview finishes loading, and label preview images with the preset name and \"preview\".\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "scoped-region-edit",
      "name": "Scoped region edit",
      "family": "Tuners",
      "component": "RegionEditor",
      "prompt": "# Build the \"Scoped region edit\" pattern: `RegionEditor`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nConstrain generation to a selected region, layer, or range so the rest is guaranteed untouched.\n\nUse it when: Image, video, code and document editing where the user has already accepted most of the output.\n\n## What to build\nAn 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.\n\n### Props\n- `asset`: { id: string; version: string; previewUrl?: string }, the approved original the edit is applied to.\n- `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.\n- `selectedRegionId?`: string and `onSelectRegion(id: string | null)`, for controlled selection.\n- `instruction`: string and `onInstructionChange(v: string)`, the change described for the selected region.\n- `status`: \"idle\" | \"selected\" | \"generating\" | \"review\" | \"error\", controlled by the parent.\n- `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.\n- `onGenerate(regionId: string, instruction: string)`: asks the parent to generate a patch for that region only. The component never regenerates or composites anything itself.\n- `onKeep()`, `onDiscard()` and `onCompareToggle(showOriginal: boolean)`: callbacks for the review step.\n\n### States it must render\n- Idle: the asset with its version label; the instruction field and Generate are disabled, and a note reads \"Nothing selected. Everything is locked.\"\n- 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.\"\n- Generating: a working indicator sits on the selected region only, Generate shows \"Generating in mug\" and other regions cannot be selected.\n- 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\".\n- 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.\n- Error: the original stays untouched and visible, with an inline message and Retry.\n\n### Interactions\n- Clicking a region (or pressing Enter on it) selects it; clicking it again clears the selection.\n- Generate calls `onGenerate` with the selected region id and instruction; it is disabled with no selection or an empty instruction.\n- Show original swaps the view to the approved version and back, calling `onCompareToggle`, so the person can check the surroundings themselves.\n- Keep calls `onKeep` (the parent saves a new version); Discard calls `onDiscard` and restores the approved view.\n\n### Edge cases\n- Overlapping or nested regions: selecting one outlines only that one and names it; never merge them silently.\n- The parent cannot compute `outsideChanged`: show \"Unchanged outside: not verified\" instead of a reassuring zero.\n- Several regions edited in sequence before saving: list each changed region in the receipt, and keep the outside count relative to the approved original.\n- 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.\n\n## Must avoid\nClaiming a scoped edit but re-rendering the whole asset, quietly changing things the user had approved.\n\n## Engineering constraint\nComposite the patch client-side over the original; keep the untouched pixels or tokens byte-identical.\n\n## Accessibility\n- 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.\n- Announce the review receipt (what changed and the outside count) when generation finishes, and move focus to it.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "model-and-effort-switch",
      "name": "Model and effort switch",
      "family": "Tuners",
      "component": "ModelEffortPicker",
      "prompt": "# Build the \"Model and effort switch\" pattern: `ModelEffortPicker`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA user-facing choice between speed, cost and depth, replacing a hidden routing decision.\n\nUse it when: Products with a real quality-latency spread and users who feel the difference.\n\n## What to build\nA compact picker that lets a person choose between faster, cheaper and deeper answers, with each option described by its tradeoff rather than only a model name. It can re-ask the last question at the new setting and keep both answers. It renders as a button in a composer that opens a menu.\n\n### Props\n- `options`: Array<{ id: string; label: string; description: string; speed: string; cost: string; modelName?: string }>, e.g. { label: \"Deep\", description: \"Works through the data step by step\", speed: \"about 40 s\", cost: \"6 credits\" }.\n- `selectedId`: string and `onSelect(id: string)`, the controlled choice.\n- `effort?`: { levels: Array<{ id: string; label: string; description: string }>; selectedId: string; onSelect(id: string) }, an optional effort scale for options that support it.\n- `balance?`: string, e.g. \"412 credits left\".\n- `canRerun?`: boolean and `onRerun(id: string)`: re-ask the last question with the chosen option. The component never sends anything itself.\n- `status`: \"closed\" | \"open\" | \"rerunning\".\n- `onSwitchLogged?(from: string, to: string)`: called on every change so the parent can record it.\n\n### States it must render\n- Closed: the trigger shows the current option label and its speed, e.g. \"Fast, about 2 s\".\n- Open: each option shows label, one-line tradeoff, speed and cost; the current one is checked; an underlying model name, if given, is secondary text.\n- Effort expanded: for options with effort, a segmented scale with a line explaining the selected level.\n- Rerunning: the trigger shows progress; the earlier answer stays in the thread.\n- Unavailable option: dimmed with the reason, e.g. \"Not enough credits\".\n\n### Interactions\n- Choosing an option calls `onSelect` and `onSwitchLogged`, then closes the menu.\n- If `canRerun`, the menu offers \"Re-ask with Deep\" which calls `onRerun`; it states that both answers will stay.\n- Arrow keys move through options; Enter selects; Escape closes without change.\n\n### Edge cases\n- An option with no `description`: still render its speed and cost; never show a bare model name as the only text.\n- Cost exceeding the balance: option disabled with the shortfall stated.\n- The model lineup changes: options come from props, so labels stay tradeoff-based even when the model behind them changes.\n- An \"Auto\" option: describe what it chooses between and show which one it used after each answer, if the parent supplies it.\n\n## Must avoid\nCryptic model names as the only signal, forcing users to memorise a changing lineup.\n\n## Engineering constraint\nDescribe the tradeoff in the label; log switch events as your clearest signal of unmet need.\n\n## Accessibility\n- The menu is a `listbox` or `menu` with `menuitemradio`s; each option's accessible name includes its speed and cost.\n- Announce the new choice after selection, and announce re-run start and finish.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "modes-and-personas",
      "name": "Modes and personas",
      "family": "Tuners",
      "component": "ModeSwitcher",
      "prompt": "# Build the \"Modes and personas\" pattern: `ModeSwitcher`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nNamed modes that swap instructions, tools and tone for a context of use rather than a model.\n\nUse it when: One product serving distinct jobs — research, coding, drafting, tutoring.\n\n## What to build\nA switcher for named modes, where each mode is a different setup of instructions, tools, data access and output, not just a tone. It shows exactly what changes when a person switches, so the choice is meaningful. It renders as tabs or a menu above a composer or work area.\n\n### Props\n- `modes`: Array<{ id: string; name: string; purpose: string; version: string; instructions: string; tools: Array<{ id: string; label: string }>; dataScope: string; output: string }>, from versioned config.\n- `selectedId`: string and `onSelect(id: string)`, the controlled current mode.\n- `showDetails?`: boolean, expands the configuration summary under the switcher (default true on first use).\n- `status`: \"ready\" | \"switching\" | \"error\".\n- `onViewConfig?(id: string)`: opens the full mode definition elsewhere.\n- `labels?`: Partial<Record<\"tools\" | \"data\" | \"output\" | \"instructions\", string>>.\n\n### States it must render\n- Ready: the current mode marked, with its purpose and a summary: instructions in one line, the tools it can use as chips, the data it can read, and what it hands back.\n- Compare on hover or focus: the summary for the other mode, with what differs from the current mode marked (tools added or removed, wider or narrower data).\n- Switching: the new mode is marked and the summary updates; the composer stays usable.\n- Collapsed: only mode names and the current version label.\n- Error: the mode could not load; stay on the previous mode and say so.\n\n### Interactions\n- Selecting a mode calls `onSelect`; the summary updates to that mode's config.\n- A details toggle shows or hides the summary.\n- Arrow keys move between modes; selection follows focus only if switching is cheap, otherwise Enter confirms.\n\n### Edge cases\n- Two modes with identical tools, data and output: render a visible note \"Same capabilities, different instructions\" so the difference is honest.\n- Switching mid-conversation: say whether earlier messages stay and that new replies use the new mode; mark each reply with the mode that produced it.\n- A mode with no tools: show \"No tools\" explicitly rather than an empty list.\n- Config version changes: show the new version label next to the mode name.\n\n## Must avoid\nModes that only change tone while the actual capability is identical, so the choice is theatre.\n\n## Engineering constraint\nDefine modes as versioned config: system prompt, tool allowlist, retrieval scope, defaults.\n\n## Accessibility\n- Modes are a `tablist` (or radio group) with the summary as the associated panel; the panel's heading names the mode.\n- Tool added or removed markers are text (\"adds\", \"removes\"), not colour only.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "connectors",
      "name": "Connectors",
      "family": "Tuners",
      "component": "ConnectorList",
      "prompt": "# Build the \"Connectors\" pattern: `ConnectorList`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nExplicit integrations that let the agent reach real systems, with per-connector state and scope.\n\nUse it when: Assistants whose value depends on the user's own data living in other tools.\n\n## What to build\nA list of the outside systems an assistant can reach, each with its own state, permission scope and last sync. It can also show, per answer, which connectors were read and which were skipped. It renders on a settings page, in a side panel, or under an answer.\n\n### Props\n- `connectors`: Array<{ id: string; name: string; icon?: ReactNode; status: \"connected\" | \"syncing\" | \"failed\" | \"off\"; scope: string[]; access: \"read\" | \"readWrite\"; lastSyncAt?: Date; lastGoodSyncAt?: Date; error?: string }>, e.g. scope [\"tickets\", \"macros\"].\n- `usage?`: Array<{ connectorId: string; read: string | null; skippedReason?: string }>, what one answer actually read from each, e.g. \"3 tickets\".\n- `variant`: \"settings\" | \"answer\", a full management list or a compact per-answer footer.\n- `onConnect(id: string)`, `onReconnect(id: string)`, `onDisconnect(id: string)` and `onEditScope(id: string)`: the component never authenticates or changes access itself.\n- `summary?`: string, e.g. \"3 of 4 healthy\".\n- `now?`: Date, for relative times in tests.\n\n### States it must render\n- Connected: name, \"Read-only\" or \"Read and write\", the scope list and \"Synced 4 min ago\".\n- Syncing: a progress marker with the previous sync time still shown.\n- Failed: this card only shows the error, when it started and the last good sync, with Reconnect. Other connectors are unaffected.\n- Off: \"Not connected\" with Connect; it is never shown as available.\n- Answer footer: one line per connector saying what was read (\"1 account, 2 contracts\"), skipped and why, or off.\n\n### Interactions\n- Reconnect, Connect, Disconnect and Edit scope call their callbacks with the connector id.\n- Disconnect asks for confirmation, stating which scopes will stop being available.\n- In the answer variant, selecting a connector line expands the items that were read, if the parent provides them in `usage`.\n\n### Edge cases\n- A connector that has never synced: show \"Not synced yet\" instead of a blank time.\n- A stale sync (older than the parent's threshold): show the time in amber, so \"connected\" never implies fresh.\n- A failure that affects only part of the scope: show the failing scope item, not a global error.\n- An answer that used no connectors at all: the footer says so explicitly.\n\n## Must avoid\nA connected-account list with no indication of what was actually read or when it last synced.\n\n## Engineering constraint\nShow last-sync and permission scope per connector; failures need per-connector, not global, errors.\n\n## Accessibility\n- Each connector is a list item whose accessible name includes its status, e.g. \"Team chat, sync failed\".\n- Status is conveyed in text as well as colour or icon; a change to failed is announced politely with the connector name.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "autonomy-level-selector",
      "name": "Autonomy level selector",
      "family": "Governors",
      "component": "AutonomyLevelControl",
      "prompt": "# Build the \"Autonomy level selector\" pattern: `AutonomyLevelControl`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nAn explicit spectrum from suggest-only to review-after, set by the user rather than assumed by the product.\n\nUse it when: Agents that will run repeatedly, where trust should grow with demonstrated reliability.\n\n## What to build\nA control that lets a person choose how much an agent may do on its own, from suggest-only through ask-first to act-then-report. It shows what each level would mean for the work currently queued, which limits apply at every level, and the track record that unlocks higher levels. It renders in agent settings or a run panel.\n\n### Props\n- `levels`: Array<{ id: string; name: string; description: string; locked?: { reason: string } }>, ordered from least to most autonomous.\n- `currentLevelId`: string and `onChangeLevel(id: string)`, controlled. The component only requests a change; the parent enforces the level as server-side policy.\n- `status`: \"idle\" | \"saving\" | \"error\", for the pending change.\n- `queuedItems?`: Array<{ id: string; label: string; outcomeByLevel: Record<string, { text: string; kind: \"draft\" | \"ask\" | \"act\" }> }>, what the agent would do with each queued item at each level.\n- `policies?`: Array<{ text: string; setBy?: string }>, limits that override every level, e.g. \"Refunds over $500 always need a person\".\n- `trackRecord?`: { periodLabel: string; total: number; approvedUnchanged: number; edited: number; undone: number }, the evidence for raising the level.\n- `history?`: Array<{ from: string; to: string; by: string; at: Date }>, past changes, newest first.\n\n### States it must render\n- Default: levels as an ordered scale with the current one checked, a badge \"Level 2: Ask first\", and the queue preview showing each item's outcome, e.g. \"Asks you\".\n- Previewing a level: hovering or focusing another level updates the queue preview and a count such as \"You approve 1 of 4\", without changing anything.\n- Locked level: a disabled option with its unlock condition, e.g. \"Unlocks after 30 days at level 3 with under 1% undone\".\n- Saving: the new level shows as pending; queued items keep their place.\n- Changed: a log line \"Lowered from Act, then report to Ask first by you at 10:42. All 4 queued items kept their place.\"\n- Error: the previous level stays checked with an inline message and Retry.\n\n### Interactions\n- Selecting a level calls `onChangeLevel(id)`; lowering a level is always one action, never behind a confirmation or a lock.\n- Raising to the most autonomous unlocked level asks for one confirmation that lists what will now happen without asking.\n- Items whose outcome is governed by a policy show that policy next to the outcome, e.g. \"Asks you, over $500 limit\", at every level.\n- Arrow keys move the selection along the scale.\n\n### Edge cases\n- No single \"autonomous\" switch: every level is a distinct option, and there is always a way back to a lower one.\n- An item already in progress when the level changes: show it finishes under the level it started with, and say so.\n- A level the person may not choose because an admin capped it: show it disabled with who set the cap.\n- Empty queue: the preview says \"Nothing queued\" and the levels still describe behaviour in words.\n\n## Must avoid\nA single \"autonomous\" master switch that grants everything at once with no ratchet back down.\n\n## Engineering constraint\nEnforce levels server-side as policy, not in the client; changing level must not lose queued work.\n\n## Accessibility\n- The scale is a `radiogroup`; each option's name includes level number, name and description, and locked options use `aria-disabled` with the unlock reason as description.\n- Announce the change log line after a level is saved.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "action-approval-gate",
      "name": "Action approval gate",
      "family": "Governors",
      "component": "ApprovalRequest",
      "prompt": "# Build the \"Action approval gate\" pattern: `ApprovalRequest`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nThe agent pauses before a consequential step and presents a decision-ready packet to a human.\n\nUse it when: Any irreversible or externally visible action: send, publish, delete, charge, deploy, grant access.\n\n## What to build\nA card the agent shows when it has paused before a consequential action. It gives a person everything needed to decide in one place (what will happen, why, the evidence, and what happens if it is wrong), then returns a single decision. It renders inline in a run log, chat or inbox.\n\n### Props\n- `action`: string, the action in plain words, e.g. \"Send a $1,240 refund to the customer\".\n- `reason`: string, why the agent wants to do it.\n- `evidence`: Array<{ label: string; detail: string }>, the facts the agent relied on.\n- `ifWrong`: string, the consequence and reversibility, e.g. \"Reversible for 24 hours, then final\".\n- `expiresAt?`: Date, when the request lapses, and `defaultOnExpiry`: \"deny\" | \"approve\" (default \"deny\").\n- `editable?`: { label: string; value: string; validate?: (v: string) => string | null }, one value the approver may change before approving (amount, recipient, date).\n- `risk`: \"low\" | \"medium\" | \"high\", which changes emphasis only and never hides information.\n- `status`: \"pending\" | \"editing\" | \"submitting\" | \"approved\" | \"rejected\" | \"expired\", controlled by the parent.\n- `decidedBy?`: { name: string; at: Date; editedValue?: string }, shown once decided.\n- `onApprove(editedValue?: string)`, `onReject(reason?: string)` and `onEditStart()`: callbacks. The component never performs the action itself.\n\n### States it must render\n- Pending: action, reason, evidence and \"if wrong\" are visible before the buttons, with a live countdown when `expiresAt` is set.\n- Editing: the editable value as an input with inline validation, plus Confirm and Cancel.\n- Submitting: buttons disabled with a busy indicator; a second click does nothing.\n- Approved: collapses to a one-line receipt (who, when, and the edited value next to the original if it changed).\n- Rejected: a one-line receipt with the optional reason.\n- Expired: a one-line receipt saying it was denied (or approved) by default at the deadline.\n\n### Interactions\n- Approve, Edit and Reject are separate, clearly labelled buttons. Approve is the primary one; Reject is never styled as primary.\n- Edit swaps the buttons for the input. Confirm calls `onApprove(value)` only when `validate` returns null.\n- Reject may ask for a short optional reason before calling `onReject`.\n- The countdown ticks every second, but expiry is applied through the `status` the parent sets, not by the component acting on its own.\n\n### Edge cases\n- Very long action text or evidence lists: wrap, never truncate the action; collapse evidence beyond 4 items behind \"Show all\".\n- `expiresAt` already in the past on mount: render Expired immediately.\n- The parent sets `status` back to pending after a failed submit: show an inline error and re-enable the buttons.\n- Several requests in one list: each keeps its own state and focus.\n\n## Must avoid\nGating everything until reviewers rubber-stamp — oversight in appearance, none in substance.\n\n## Engineering constraint\nApproval must happen before side effects, and the run must resume from the same state, not restart.\n\n## Accessibility\n- The card is a `region` labelled by the action text. Don't announce the countdown every second; announce at 5 minutes, 1 minute and expiry.\n- After a decision, move focus to the receipt line so keyboard users know what happened.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "scope-and-tool-permissions",
      "name": "Scope and tool permissions",
      "family": "Governors",
      "component": "PermissionInventory",
      "prompt": "# Build the \"Scope and tool permissions\" pattern: `PermissionInventory`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA visible inventory of the tools, data and destinations the agent may touch, editable by the user.\n\nUse it when: Agents with connectors, and any enterprise deployment where access is the real risk.\n\n## What to build\nA panel listing every tool, data source and destination an agent may touch, grouped as read, act and send to, each with its own switch and recent use. It also surfaces in-context requests when the agent needs access it doesn't have, so consent resurfaces as capabilities expand. It renders in agent settings and inline in a run when a request is pending.\n\n### Props\n- `agent`: { id: string; name: string }, the identity permissions are bound to.\n- `groups`: Array<{ name: string; items: Array<{ id: string; title: string; description: string; granted: boolean; risky?: boolean; lastUsed?: Date; offDescription?: string }> }>, where `offDescription` explains the fallback, e.g. \"Off: drafts replies for a person to send\".\n- `onToggle(itemId: string, granted: boolean)`: requests a change. The parent enforces permissions server-side against the agent identity.\n- `pendingRequests?`: Array<{ id: string; itemId: string; title: string; reason: string; context: string; scopeOptions: Array<{ id: string; label: string }> }>, e.g. a one-off \"Allow for this ticket only\" alongside \"Always allow\".\n- `onGrantRequest(requestId: string, scopeOptionId: string)` and `onDenyRequest(requestId: string)`: callbacks.\n- `grantedBy?`: { name: string; at: Date }, shown under the summary.\n- `savingIds?`: string[], items whose change is in flight.\n\n### States it must render\n- Default: a summary line \"Right now the agent can use 3 read sources, 2 actions, 1 destination\", then grouped switches with each item's last use or \"Not granted\".\n- Pending request: an amber card at the top, \"The agent is asking for more access: Issue refunds\", with the exact context (\"For ticket #1042: refund $1,240\"), why now (\"The run paused at this step\"), scope choices and Deny.\n- Item turned off: the row shows its `offDescription`, the summary count drops, and a note confirms \"Applies to runs already in progress\".\n- Saving: the switch shows its new position with a busy indicator; a failed save reverts it with an inline error.\n- Empty: \"This agent has no access yet\" with each group still listed and all switches off.\n\n### Interactions\n- Each switch calls `onToggle`; granting a risky item asks for one confirmation that names the risk, revoking never does.\n- Request buttons call `onGrantRequest` with the chosen scope; the narrowest scope (this item only) is the first option.\n- Resolved requests collapse into a one-line note, e.g. \"Allowed for #1042 only by you at 10:12\".\n\n### Edge cases\n- A capability added after setup (new connector, new action) appears as a pending request, never as already granted.\n- Items controlled by an admin policy: show the switch locked with who set it.\n- Several pending requests: list them all, each with its own decision, oldest first.\n- An item never used in 30 days: show \"Not used in 30 days\" so people can trim unused access.\n\n## Must avoid\nOne bundled consent screen at install that never resurfaces as capabilities expand.\n\n## Engineering constraint\nBind permissions to agent identity so checkpoints are enforceable; re-prompt on scope expansion.\n\n## Accessibility\n- Switches use `role=\"switch\"` with `aria-checked`; each label includes the item title and its group, e.g. \"Act: Issue refunds\".\n- Announce a new pending request politely and make the request card a labelled `region`.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "interrupt-and-kill-switch",
      "name": "Interrupt and kill switch",
      "family": "Governors",
      "component": "RunStopControl",
      "prompt": "# Build the \"Interrupt and kill switch\" pattern: `RunStopControl`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA always-present stop that halts the run, states what already happened, and offers to undo it.\n\nUse it when: Long multi-step runs, and anything touching production systems.\n\n## What to build\nA 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.\n\n### Props\n- `status`: \"running\" | \"stopping\" | \"stopped\" | \"finished\" | \"stop-failed\", controlled by the parent from executor events.\n- `onStop()`: requests cancellation. The component never assumes the run has stopped; it waits for `status`.\n- `shortcut?`: string, the keyboard shortcut shown next to Stop, e.g. \"Esc\".\n- `progress?`: { done: number; total?: number; unitLabel: string; current?: string }, e.g. 64 of 140 tickets closed.\n- `stopReport?`: { stoppedBy: string; at: Date; confirmedByExecutor: boolean; effects: Array<{ label: string; count: number; reversible: boolean; note?: string }>; cancelledCall?: string; untouched: number; checkpointId?: string }.\n- `onUndo?(effectLabel: string)`: reverses a reversible effect, e.g. reopen closed tickets.\n- `onResume?(checkpointId: string)`: continues from the checkpoint instead of starting over.\n\n### States it must render\n- Running: a red-outlined Stop button with its shortcut, always in view (sticky), next to the live progress, e.g. \"64 of 140 closed\".\n- Stopping: Stop is disabled with \"Stopping, waiting for current call to finish\"; progress freezes but the panel does not claim the run has halted.\n- 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\".\n- Stop failed: a red alert \"The run did not stop\" with a retry and an escalation path, never a silent return to Running.\n- Finished: Stop is replaced by a completion line, e.g. \"Finished: 140 tickets closed, 140 emails sent\".\n\n### Interactions\n- Stop and its shortcut call `onStop` once with no confirmation dialog; pressing again while stopping does nothing.\n- 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.\n- Resume calls `onResume(checkpointId)` and is labelled with where it continues, e.g. \"Resume from ticket 65\".\n\n### Edge cases\n- Calls that complete between pressing Stop and confirmation: they appear in the effects list, not in \"untouched\".\n- A run with no checkpoint: hide Resume and say \"This run can't resume; restart it to continue\".\n- Stop pressed in the last moments as the run finishes: show Finished with a note that it completed before the stop took effect.\n- The shortcut must not fire while focus is in a text input that uses the same key.\n\n## Must avoid\nA stop button that only hides the UI while the agent keeps executing tool calls in the background.\n\n## Engineering constraint\nPropagate cancellation to tool executors; record a checkpoint so the user can resume rather than redo.\n\n## Accessibility\n- Stop is a real button with an accessible name including its shortcut; it stays in the tab order and reachable for the whole run.\n- Announce Stopping, then Stopped with the effect counts, and move focus to the stopped panel heading.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "dry-run-preview",
      "name": "Dry run preview",
      "family": "Governors",
      "component": "DryRunPlan",
      "prompt": "# Build the \"Dry run preview\" pattern: `DryRunPlan`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nShow the full sequence the agent intends to execute, with side effects marked, before anything runs.\n\nUse it when: Bulk operations, migrations, and workflows a user cannot easily inspect afterwards.\n\n## What to build\nA 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.\n\n### Props\n- `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.\n- `effectTotals`: Array<{ label: string; value: string }>, e.g. \"Credits issued: $4,320\", \"Customer emails: 36\", \"Records deleted: 0\".\n- `onToggleStep?(stepId: string, included: boolean)`: for optional steps; the parent returns a new plan with a new id.\n- `status`: \"preview\" | \"running\" | \"done\" | \"diverged\" | \"error\", controlled by the parent.\n- `stepResults?`: Record<string, { state: \"running\" | \"done\" | \"skipped\" | \"failed\"; detail: string; matchesPreview: boolean }>.\n- `onRun(planId: string)`: runs exactly that plan id. The component never executes anything itself.\n\n### States it must render\n- 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\".\n- 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\".\n- Running: steps tick through running and done in order, each with its actual result.\n- 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\".\n- 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.\n- Error: the failed step is marked, later steps show \"Not run\".\n\n### Interactions\n- Run calls `onRun(plan.id)`; the button label names the plan, e.g. \"Run this exact plan\".\n- 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.\n- Expanding a step shows its full detail (affected records, template, amounts).\n\n### Edge cases\n- 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.\n- Steps whose counts depend on live data: show the count at preview time and state that the run stops and asks if it differs.\n- No side effects at all: say \"This run only reads data\" in the totals.\n- Irreversible effects (external messages, deletes) are labelled red and listed first in the totals.\n\n## Must avoid\nA plan that diverges from execution, teaching users the preview is decorative.\n\n## Engineering constraint\nGenerate the preview from the same plan object the executor consumes, or it will drift.\n\n## Accessibility\n- Steps are an ordered list; each step's effect and reversibility are in its text, not only in colour or icon.\n- Announce Done or Diverged, including the matched or mismatched step.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "cost-estimate",
      "name": "Cost estimate",
      "family": "Governors",
      "component": "CostEstimate",
      "prompt": "# Build the \"Cost estimate\" pattern: `CostEstimate`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nThe price of an action — credits, tokens, minutes, dollars — shown before the user commits to it.\n\nUse it when: Metered products and long agent runs where a careless click is expensive.\n\n## What to build\nA panel that shows what an action will cost (credits, tokens, minutes or money) before a person commits, with the range, the remaining balance after the run, and any cap it would exceed. Changing scope or depth updates the figure immediately. It renders next to the Run button of a metered action.\n\n### Props\n- `estimate`: { amount: number; unit: string; low?: number; high?: number; moneyEquivalent?: string; durationLabel?: string; basis: string }, where `basis` explains the source, e.g. \"Estimated from 2,140 runs of the same shape\".\n- `balance?`: { remaining: number; usedThisPeriod?: number; allowance?: number }, used to draw the before and after bar.\n- `cap?`: { amount: number; label: string; owner?: string }, a hard per-run ceiling enforced by the server; the component only displays it.\n- `options?`: Array<{ id: string; label: string; choices: Array<{ id: string; label: string; hint?: string }>; selectedId: string }>, scope and depth controls that change the estimate.\n- `onOptionChange(optionId: string, choiceId: string)`: the parent recalculates and passes a new `estimate`.\n- `status`: \"estimating\" | \"ready\" | \"over-budget\" | \"running\" | \"done\" | \"error\", controlled by the parent.\n- `actual?`: { amount: number }, the final spend once done.\n- `onRun()`: callback for the commit button, whose label repeats the cost. The component never starts the run itself.\n\n### States it must render\n- Estimating: the previous figure dims with a quiet indicator; Run is disabled until the new figure arrives.\n- Ready: \"This run will cost about 1,248 credits\" with \"about $12.48, about 6 min, +/-6%\", a bar showing used, this run and left after, and the basis line.\n- Over budget: the amount exceeds balance or cap; the bar segment and text turn red, the reason is stated (\"Over the team's 2,000-credit cap per run\") with a suggestion to narrow scope, and Run reads \"Over budget\" and is disabled.\n- Running: Run is replaced by a progress line; options are locked.\n- Done: actual next to estimate, e.g. \"Spent 1,231 of an estimated 1,248 credits\".\n- Error: \"Couldn't estimate this run\" with Retry; Run stays disabled rather than running blind.\n\n### Interactions\n- Changing a scope or depth choice calls `onOptionChange`; the figure updates without a page reload and a brief highlight marks the change.\n- The Run button label always includes the amount, e.g. \"Run on 312 tickets, 1,248 credits\", so the commit point and the price are the same element.\n- A \"Try on 10 first\" choice, when provided in `options`, is shown first as the cheapest way to check.\n\n### Edge cases\n- No historical runs of this shape: show a wider range and the basis \"Rough estimate, no past runs to compare\", never a falsely precise number.\n- Actual exceeds the estimate by more than the stated range: show the difference plainly in Done.\n- Free actions: show \"Free\" rather than hiding the panel, so people learn which buttons cost nothing.\n- Balance unknown: hide the bar and keep the amount.\n\n## Must avoid\nRevealing spend only in a monthly invoice, teaching users to fear every button.\n\n## Engineering constraint\nEstimate from historical runs of the same shape; enforce a hard budget ceiling server-side.\n\n## Accessibility\n- The figure sits in an `aria-live=\"polite\"` region that announces the new total after options change, debounced so rapid changes announce once.\n- The bar has a text equivalent, e.g. \"7,200 used, this run 1,248, 3,552 left after\".\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "draft-mode",
      "name": "Draft mode",
      "family": "Governors",
      "component": "DraftCompare",
      "prompt": "# Build the \"Draft mode\" pattern: `DraftCompare`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA cheap, fast, explicitly provisional pass for exploring before committing to a full-quality run.\n\nUse it when: Expensive generation — video, high-res images, deep research, large refactors.\n\n## What to build\nA view for cheap, fast draft outputs that are unmistakably marked as provisional, with a single step to finalise a chosen draft at full quality. It lets a person compare a few directions before spending on the real run, and blocks drafts from being exported as if they were final. It renders in a generation or research workspace.\n\n### Props\n- `drafts`: Array<{ id: string; title: string; summary: string; body: React.ReactNode; seed: string; limitations: string[] }>, where `limitations` lists what the cheap pass skipped, e.g. \"Numbers not checked\".\n- `selectedId`: string and `onSelect(id: string)`, controlled.\n- `draftCost?`: string, what the drafts cost, e.g. \"3 drafts, 12 credits, 38 s\".\n- `finalCost`: string, the cost of the full pass, e.g. \"120 credits, about 2 min\".\n- `finalPassIncludes?`: string[], what the full pass adds, e.g. \"23 sources read in full\".\n- `status`: \"drafts\" | \"finalising\" | \"final\" | \"error\", controlled by the parent.\n- `final?`: { body: React.ReactNode; fromDraftId: string; seed: string }, the full-quality result.\n- `onFinalise(draftId: string, seed: string)` and `onExport()`: callbacks. The component never generates or exports anything itself.\n\n### States it must render\n- Drafts: draft cards each carry a persistent \"Draft\" badge, a provisional visual treatment (for example a hatched or tinted frame) and the limitations line \"Fast pass from 6 sources. Numbers not checked. Not for sharing.\"\n- Draft selected: the chosen draft is shown larger with its gaps marked inline (\"source?\"), plus the Finalise button showing `finalCost` and what the full pass adds.\n- Finalising: draft choice is locked and progress reads \"Finalising, reading 23 sources\".\n- Final: the draft badge and treatment are removed, a line says \"Finalised from draft B, same outline and seed\", and Export becomes enabled.\n- Error: the draft remains, marked as draft, with Retry finalise.\n\n### Interactions\n- Clicking a draft card selects it; arrow keys move between cards.\n- Finalise calls `onFinalise(draftId, seed)` so the full run keeps the draft's seed and outline.\n- Export is disabled while any draft is shown, with the reason \"Drafts can't be exported\" next to it; it calls `onExport` only in Final.\n\n### Edge cases\n- Copying text out of a draft: copied content carries a \"Draft\" prefix or note, so it can't leave looking final.\n- Final output that differs noticeably from the draft outline: show \"Changed from draft\" markers rather than implying they match.\n- A single draft: skip the comparison layout but keep the badge and limitations.\n- Printing or screenshots: the draft badge stays visible in print styles.\n\n## Must avoid\nDraft output that looks final, so users ship it without knowing they saw the cheap version.\n\n## Engineering constraint\nMark draft artifacts in metadata and carry the seed forward so the final run matches the draft.\n\n## Accessibility\n- Draft cards form a `radiogroup`; each card's accessible name starts with \"Draft:\" so the provisional status is not only visual.\n- Announce the switch from draft to final, including which draft it came from.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "inline-citations",
      "name": "Inline citations",
      "family": "Trust indicators",
      "component": "CitedAnswer",
      "prompt": "# Build the \"Inline citations\" pattern: `CitedAnswer`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nNumbered or hovering markers that bind each claim to a retrievable source.\n\nUse it when: Any factual, retrieval-backed, or high-stakes answer.\n\n## What to build\nRenders an AI answer whose claims carry numbered citation markers. Selecting a marker highlights exactly the words it supports and opens the cited passage (not just the document) in a source list, so checking a claim takes one click.\n\n### Props\n- `segments`: Array<{ text: string; citationIds?: string[] }>, the answer split into spans; a span with `citationIds` is a supported claim.\n- `sources`: Array<{ id: string; title: string; meta?: string; url?: string; passage: string; highlight: [start: number, end: number] }>, where `highlight` is the character range inside `passage` that supports the claim.\n- `selectedId?`: string and `onSelect(id: string | null)`, for controlled selection so a parent can sync other UI.\n- `layout`: \"side\" | \"below\", where the source list renders; use \"below\" under 640 px.\n- `showUnsupported?`: boolean, marks claims that have no citation.\n- `onOpenSource?(id: string)`: opens the source at the passage anchor (a deep link), never at the top of the file.\n\n### States it must render\n- Default: answer text with small numbered markers after each supported claim; the source list shows titles and meta only.\n- Claim selected: the supported span is highlighted, its marker is pressed, and the matching source expands to show the passage with the supporting words marked.\n- Unsupported claim: with `showUnsupported`, a span without citations gets a subtle \"no source\" marker.\n- Passage unavailable: a source without a passage says \"Passage not available\" and still links to the document.\n\n### Interactions\n- Clicking a marker or a source header selects it; clicking the selected one again clears the selection.\n- Hovering a marker previews its highlight without changing the selection.\n- Arrow keys move between markers when one has focus; Escape clears the selection.\n- A claim with several citations shows adjacent markers (1, 2), each selectable on its own.\n\n### Edge cases\n- Two claims citing the same source: selecting either highlights only its own span, and the same passage.\n- A claim that wraps across lines or list items: the highlight follows the text without breaking layout.\n- A long passage: show about 300 characters around the highlight, with \"Show full passage\".\n- A streaming answer: markers appear as segments arrive, without shifting earlier text.\n\n## Must avoid\nCitations that point at a whole document instead of the passage, so verifying costs more than rewriting.\n\n## Engineering constraint\nReturn spans with offsets from retrieval, and deep-link to the anchor, not the file.\n\n## Accessibility\n- Markers are buttons with labels like \"Source 2: Q3 retention review\" and `aria-pressed`.\n- The link between claim and passage is shown with more than colour (an underline or outline as well).\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "confidence-signalling",
      "name": "Confidence signalling",
      "family": "Trust indicators",
      "component": "ConfidenceFieldList",
      "prompt": "# Build the \"Confidence signalling\" pattern: `ConfidenceFieldList`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA visible estimate of certainty — level, band, or hedged language — so users know when to check.\n\nUse it when: Classification, extraction, medical, legal and financial flows where silent certainty is dangerous.\n\n## What to build\nA list of AI-extracted or AI-classified values, each with a confidence level shown as a word and a band, plus a short reason for anything below high. It directs review to the uncertain values and can block a dependent action until they are checked. It renders beside a source document in extraction, classification or review flows.\n\n### Props\n- `fields`: Array<{ id: string; label: string; value: string; level: \"high\" | \"medium\" | \"low\"; reason?: string; alternatives?: string[]; sourceRef?: string }>, bucketed levels, not raw scores.\n- `score?`: Record<string, number>, shown only when `calibrated` is true.\n- `calibrated`: boolean, whether numeric scores are trustworthy enough to display; default false.\n- `calibration?`: string, evidence for the levels, e.g. \"Last 30 days: reviewers kept 98% of High, 81% of Medium, 54% of Low unchanged\".\n- `onResolve(fieldId: string, value: string, changed: boolean)`: logs accepted versus corrected, which the parent uses to test calibration.\n- `onFocusSource?(sourceRef: string | null)`: highlights the region of the source the value came from.\n- `blockingAction?`: { label: string; onConfirm(): void }, an action enabled only once medium and low fields are resolved.\n- `status`: \"loading\" | \"ready\" | \"error\".\n\n### States it must render\n- Loading: field rows with placeholders; no level shown until values arrive.\n- Ready: each row shows label, value and a level chip (\"High\", \"Medium\", \"Low\") using text plus shape; medium and low rows are expanded with the reason, e.g. \"The last digit is blurred in the scan. It reads as 7, but could be 1.\"\n- Needs checking: a header count \"2 fields to check\" and the blocking action disabled with \"Check the 2 flagged fields to continue\".\n- Resolved field: the row shows \"Kept by you\" or \"Changed by you\" with the original value if different.\n- All resolved: the header turns green and the blocking action is enabled.\n- Error: \"Couldn't extract these fields\" with Retry.\n\n### Interactions\n- Low and medium rows offer \"Keep <value>\" and one button per alternative, plus a free-text edit; each calls `onResolve`.\n- Focusing a row calls `onFocusSource` so the matching area of the document is highlighted.\n- High rows can be expanded and edited too, but are not required.\n\n### Edge cases\n- Uncalibrated models must not show percentages: with `calibrated` false, scores are ignored even if passed.\n- Confidence is not evidence: when `sourceRef` is missing, show \"No source location\" next to the level rather than letting the level stand alone.\n- All fields high: show no checking prompt, and keep the calibration line visible.\n- A field the model could not read at all: show \"Not found\" as low with an empty input, never a guessed value.\n\n## Must avoid\nA precise percentage from an uncalibrated model, or confidence used as a substitute for citations.\n\n## Engineering constraint\nBucket into levels unless the score is genuinely calibrated; log accepted-vs-corrected to test it.\n\n## Accessibility\n- Each level chip has text, and the row's accessible description includes the level and reason.\n- Announce the remaining count after each resolution, e.g. \"1 field left to check\".\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "reasoning-trace",
      "name": "Reasoning trace",
      "family": "Trust indicators",
      "component": "ReasoningTrace",
      "prompt": "# Build the \"Reasoning trace\" pattern: `ReasoningTrace`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nAn expandable record of steps, tool calls and intermediate findings behind the answer.\n\nUse it when: Complex multi-step work, and debugging surfaces for power users and engineers.\n\n## What to build\nA collapsible, structured record of the steps behind an answer: plans, queries, searches, reads, skipped steps and findings, each as a typed event with a one-line result. It opens as a summary (time, sources, steps skipped) and lets a person drill into any single step's raw detail. It renders under an AI answer or in a debugging panel.\n\n### Props\n- `events`: Array<{ id: string; kind: \"plan\" | \"query\" | \"search\" | \"read\" | \"tool\" | \"skip\" | \"finding\" | \"error\"; title: string; result: string; durationMs?: number; detail?: React.ReactNode }>, typed events from the run, not free-form text.\n- `summary`: { durationLabel: string; sourceCount: number; skippedCount: number }, e.g. \"Worked for 14s, 3 sources, 1 step skipped\".\n- `expanded`: boolean and `onExpandedChange(v: boolean)`, controlled visibility of the step list.\n- `openEventId?`: string and `onOpenEvent(id: string | null)`, which step's detail is open.\n- `status`: \"running\" | \"done\" | \"error\".\n- `filterKinds?`: Array<string>, to show only certain kinds, e.g. only skipped and errors.\n\n### States it must render\n- Collapsed: one line under the answer with the summary and \"Show 6 steps\"; skipped or error counts are in amber or red text.\n- Expanded: an ordered list where each event shows a kind label (\"Query\", \"Read\", \"Skipped\"), title and one-line result, e.g. \"23 accounts, 19 had fewer than 50 seats\".\n- Step open: the selected event shows its detail (the query, a table, a quoted passage) below its row.\n- Skipped step: marked distinctly with why, e.g. \"CRM isn't connected, so sales-side reasons weren't checked\", and how that limits the answer.\n- Running: events append as they arrive with \"Working\" in the summary line.\n- Error: the failing event is marked and its message shown; earlier events remain.\n\n### Interactions\n- The summary line toggles `expanded`; clicking an event row toggles its detail via `onOpenEvent`.\n- Arrow keys move between event rows; Enter opens detail.\n- A \"Show only gaps\" control filters to skipped and error events.\n\n### Edge cases\n- Never show raw chain-of-thought as the default: free-text narration, if present, lives inside a step's detail, not as the trace itself.\n- Very long traces (more than 20 events): group consecutive events of the same kind (\"Read 8 documents\") with expand.\n- Large detail payloads (big tables, long SQL): render on demand when opened, and cap height with internal scrolling.\n- No skipped steps: say \"Nothing skipped\" so absence is explicit.\n\n## Must avoid\nDumping raw chain-of-thought as proof of rigour; long traces read as persuasive, not diagnostic.\n\n## Engineering constraint\nStructure the trace as events with types so the UI can summarise and let users drill in.\n\n## Accessibility\n- The toggle has `aria-expanded` and controls the list; each event row is a button with `aria-expanded` for its detail.\n- Event kind is part of each row's accessible name, e.g. \"Skipped: Sales call notes\".\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "source-freshness-and-coverage",
      "name": "Source freshness and coverage",
      "family": "Trust indicators",
      "component": "SourceCoverage",
      "prompt": "# Build the \"Source freshness and coverage\" pattern: `SourceCoverage`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nState when sources were updated and how much of the corpus was actually searched.\n\nUse it when: Retrieval products where staleness or thin coverage is the dominant failure mode.\n\n## What to build\nA compact coverage line attached to an AI answer that says how many sources were searched out of how many are available, and how fresh each one is, with an expandable list. When coverage is below a threshold or a key source is stale, it says so next to the answer and offers to refresh and search again. It renders under a retrieval answer.\n\n### Props\n- `sources`: Array<{ id: string; name: string; state: \"searched\" | \"stale\" | \"not-searched\" | \"not-connected\" | \"syncing\"; lastSyncedAt?: Date; matches?: number; reason?: string }>, from the index's retrieval metadata.\n- `documentCount`: number, documents actually used in the answer.\n- `threshold`: { minSources?: number; maxAgeDays?: number }, below which coverage is flagged.\n- `warning?`: { title: string; detail: string }, why the gap matters here, e.g. \"Renewal conversations usually happen on calls\".\n- `onRefresh?(sourceId: string)`: asks the parent to re-sync and re-run. The component never syncs itself.\n- `onConnect?(sourceId: string)`: for sources that aren't connected.\n- `expanded`: boolean and `onExpandedChange(v: boolean)`.\n\n### States it must render\n- Full coverage: a quiet line \"Based on 4 of 4 sources, all synced today\".\n- Thin coverage: an amber notice by the answer, \"Based on 2 of 4 sources\", with the warning detail and \"Refresh call notes and search again\".\n- Expanded list: each source with an icon and text state, e.g. \"Help desk tickets: synced 2 hours ago, 3 matches\", \"Call notes: not searched, last synced 21 Aug, 41 days ago\", \"Email: not connected\".\n- Refreshing: the source row shows \"Syncing 6 calls since 21 Aug\" and the answer dims until the parent passes the new one.\n- Updated: the count rises (\"3 of 4 sources\"), changed rows highlight briefly, and any remaining gap is still stated.\n\n### Interactions\n- The coverage line toggles the source list.\n- Refresh calls `onRefresh(sourceId)`; Connect calls `onConnect(sourceId)`.\n- Hovering a source highlights the parts of the answer drawn from it, when the parent provides that mapping.\n\n### Edge cases\n- A \"none found\" answer on thin coverage: the warning is required, since absence from two sources is not absence.\n- Sync timestamps in the future or missing: show \"Sync time unknown\" rather than \"just now\".\n- Many sources (more than 8): show the flagged ones first, then a count of the rest.\n- Refresh finds nothing new: say \"Refreshed, no new matches\" so the person knows it ran.\n\n## Must avoid\nPresenting a confident answer built on two documents as if the whole corpus was consulted.\n\n## Engineering constraint\nSurface retrieval metadata already returned by the index; flag below-threshold coverage explicitly.\n\n## Accessibility\n- Source states are words, not only icons; each row's accessible name includes name, state and age.\n- Announce when a refresh finishes and the new coverage count.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "verification-handoff",
      "name": "Verification handoff",
      "family": "Trust indicators",
      "component": "VerificationChecklist",
      "prompt": "# Build the \"Verification handoff\" pattern: `VerificationChecklist`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nThe system names exactly what a human should double-check, turning blanket doubt into one task.\n\nUse it when: Outputs that are mostly right but carry a specific risky element — a figure, a name, a legal clause.\n\n## What to build\nA short, specific list of the items in an AI output that a person should check, each tied to the exact phrase or value, with the reason it is uncertain and one-click resolutions. It also states what the system already checked, and enables the dependent action once every item is resolved. It renders beside a draft message, document or form before it is sent or submitted.\n\n### Props\n- `items`: Array<{ id: string; quote: string; anchorId: string; reason: string; resolutions: Array<{ id: string; label: string; kind: \"keep\" | \"change\" | \"remove\"; replacement?: string }> }>, derived from low-confidence or unsupported parts of the output.\n- `alreadyChecked?`: string, what passed automatic checks, e.g. \"Amount matches invoice #2210, card ending 5521\".\n- `resolved`: Record<string, { resolutionId: string; by: string }>, controlled.\n- `onResolve(itemId: string, resolutionId: string)`: the parent applies any text change. The component never edits the output itself.\n- `activeItemId?`: string and `onActivate(itemId: string | null)`, to highlight the matching phrase in the output.\n- `action`: { label: string; onConfirm(): void }, e.g. \"Send reply\", enabled when all items are resolved.\n\n### States it must render\n- Open items: a header \"2 things to check\" and numbered items, each quoting the phrase (\"3 to 5 business days\"), the specific reason (\"The card was issued in Canada; the source only covers US cards\"), and resolution buttons; the action is disabled.\n- Item active: the quoted phrase is highlighted in the output and the item is outlined.\n- Item resolved: the item collapses to \"Checked by you\" or \"Changed by you\" and the phrase shows its new state in the output.\n- All resolved: the header turns green \"Ready to send\" and the action enables.\n- Nothing to check: a single green line with `alreadyChecked`, and the action enabled; no generic disclaimer.\n\n### Interactions\n- Clicking an item or hovering its phrase in the output calls `onActivate` to link the two.\n- Each resolution button calls `onResolve`; \"keep\" resolutions are labelled as a claim of checking, e.g. \"I checked, keep it\".\n- Arrow keys move between items; the next unresolved item gets focus after one is resolved.\n\n### Edge cases\n- More than 5 items means the output is not \"mostly right\": show a notice suggesting a full review instead of a long checklist.\n- The parent regenerates the output: items whose quote no longer appears are dropped, and new ones appear unresolved.\n- Two items on overlapping text: highlight each separately and resolve independently.\n- A resolution that changes text creates new uncertainty: the parent may add a new item, which appears unresolved.\n\n## Must avoid\nA generic \"AI can make mistakes\" footer that shifts responsibility without directing attention.\n\n## Engineering constraint\nDerive the check list from low-confidence fields, not from a static template.\n\n## Accessibility\n- Each item is linked to its phrase with `aria-describedby`; the phrase is focusable and announces \"needs checking\".\n- Announce the remaining count after each resolution and when the action becomes available.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "caveat",
      "name": "Caveat",
      "family": "Trust indicators",
      "component": "Caveat",
      "prompt": "# Build the \"Caveat\" pattern: `Caveat`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA plain statement of a known limitation, placed where it changes the user's next action.\n\nUse it when: Domains where the model is reliably weak: recent events, arithmetic, legal specifics, rare languages.\n\n## What to build\nAn inline notice attached to a specific part of an AI answer that states one known limitation, why it applies here, and what to do about it. It appears only when a condition is detected (stale data, unsupported language, unchecked arithmetic) and sits next to the affected value, not in a footer. It renders inside a chat answer, report or generated document.\n\n### Props\n- `condition`: { kind: string; detectedAt?: Date }, the detected trigger, e.g. \"stale-sync\"; the component renders nothing without one.\n- `title`: string, the limitation in plain words, e.g. \"The seat count may be out of date\".\n- `detail`: string, the specific reason, e.g. \"It comes from the billing sync on 19 Sep, 12 days ago\".\n- `appliesTo`: string, a label or id for the value or passage affected, used to link the caveat to it visually.\n- `actions?`: Array<{ id: string; label: string; kind: \"fix\" | \"carry\" }>, where \"fix\" removes the cause (re-sync, recalculate) and \"carry\" keeps the caveat attached when the content leaves (copy with note).\n- `onAction(id: string)`: callback. The component never re-syncs or recalculates itself.\n- `status`: \"shown\" | \"resolving\" | \"resolved\" | \"dismissed\", controlled.\n- `resolution?`: string, what changed once fixed, e.g. \"Up to date. 6 seats were added, so the total is $2,304 higher\".\n\n### States it must render\n- Shown: an amber notice directly under or beside the affected value, which is itself marked, with title, detail and actions.\n- Resolving: the notice is replaced by progress, e.g. \"Syncing seats from billing\", and the affected value dims.\n- Resolved: the caveat disappears and a green line explains what changed; any updated value is highlighted briefly.\n- Carried: after a carry action, a confirmation shows exactly the text that went with the content, including the note.\n- Not shown: when no condition is passed, nothing renders, so there is no default disclaimer.\n\n### Interactions\n- Each action calls `onAction(id)`; the fix action is listed first.\n- Hovering or focusing the caveat highlights the value it applies to, and the reverse.\n- Dismiss, when offered, records that the person saw it and keeps a small marker on the value.\n\n### Edge cases\n- Several caveats in one answer: each sits next to its own value; never merge them into one generic block at the bottom.\n- The condition clears while the caveat is shown (data synced elsewhere): switch to Resolved with the reason.\n- The fix action fails: keep the caveat, add the error, and leave the carry action available.\n- A caveat whose value is off-screen in a long answer: keep it adjacent to the value, not pinned to the viewport.\n\n## Must avoid\nA blanket footer disclaimer on every screen, which users learn to read as decoration.\n\n## Engineering constraint\nTrigger caveats from detected conditions — stale index, unsupported language — not from a static string.\n\n## Accessibility\n- The caveat is a `note` linked to the affected value with `aria-describedby`, so screen readers hear it when they reach the value.\n- Announce Resolved with what changed.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "consent-and-data-ownership",
      "name": "Consent and data ownership",
      "family": "Trust indicators",
      "component": "CaptureConsentPanel",
      "prompt": "# Build the \"Consent and data ownership\" pattern: `CaptureConsentPanel`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nExplicit control over what the system may retain, train on, and share, stated before capture.\n\nUse it when: Anything recording people, and every enterprise or regulated deployment.\n\n## What to build\nA panel shown at the moment of sensitive capture (a recording, a transcript, an upload) that states what will be kept, for how long, who it is shared with and whether it is used for training, and collects an explicit answer from each person affected. Capture stays locked until everyone has answered, and each answer is recorded with the notice version. It renders before a call, recording or data collection starts, and as a join prompt for participants.\n\n### Props\n- `retention`: Array<{ id: string; label: string; detail: string; enabled: boolean; editable: boolean; policyNote?: string }>, e.g. transcript shared with attendees, audio deleted after 30 days, training off by workspace policy.\n- `onToggleRetention?(id: string, enabled: boolean)`: for the host, before capture starts.\n- `participants`: Array<{ id: string; name: string; role?: string; consent: \"waiting\" | \"agreed\" | \"declined\"; at?: Date }>.\n- `viewer`: \"host\" | \"participant\", which view to render.\n- `noticeVersion`: string, shown and recorded with each answer.\n- `captureStatus`: \"locked\" | \"capturing\" | \"not-capturing\", controlled by the parent, which gates the actual pipeline.\n- `onAnswer(participantId: string, allow: boolean)`: records a participant's choice. The component never starts capture itself.\n- `onRevoke?(participantId: string)`: lets a participant withdraw during capture.\n\n### States it must render\n- Host, waiting: retention list with switches, the participant list with \"Agreed 9:56\" or \"Waiting\", and the line \"Capture is locked until every attendee answers\"; the badge reads \"Not recording yet\".\n- Participant prompt: a plain list of what is kept (ticks) and what is never done (crosses, e.g. \"Never used to train AI models\"), with \"Join and allow notes\" and \"Join without recording\" as equal-weight buttons, and \"You can change this at any time. Notice v3.\"\n- Capturing: a red badge naming exactly what is being recorded, e.g. \"Recording transcript + audio\", and when capture started.\n- Declined: capture stays off for the session, with \"Not recording: one attendee declined\".\n- Consent record: who agreed to what, the basis (\"Join prompt, notice v3\") and when.\n\n### Interactions\n- Participant buttons call `onAnswer`; neither option is pre-selected or styled as the default.\n- Host retention switches are disabled once capture has started; changes after that require a new notice.\n- Revoke during capture calls `onRevoke` and the badge switches immediately to not capturing once the parent confirms.\n\n### Edge cases\n- A participant who joins late: capture shows as paused or excluded for them until they answer.\n- Everyone agreed but the host turned every retention item off: say \"Nothing is kept\" rather than showing a recording badge.\n- The notice changes between answers: earlier answers are marked as given to the older version and those participants are asked again.\n- Training use governed by workspace policy: show the switch locked with the policy note.\n\n## Must avoid\nConsent buried at signup that never resurfaces when the sensitive capture actually happens.\n\n## Engineering constraint\nConsent state must gate the pipeline, not just the UI; log the basis and version of what was agreed.\n\n## Accessibility\n- The participant prompt is a dialog labelled by its heading, with focus on the heading, not on the allow button.\n- Announce capture starting and stopping, and each participant's answer in the host view.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "background-run-tray",
      "name": "Background run tray",
      "family": "Dark matter",
      "component": "RunTray",
      "prompt": "# Build the \"Background run tray\" pattern: `RunTray`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA persistent list of agent runs with status, so async work is inspectable instead of invisible.\n\nUse it when: Any agent that outlives the session or runs while the user is elsewhere.\n\n## What to build\nA persistent panel listing every agent run, running or finished, with its status, age and outcome, so asynchronous work stays inspectable after any toast has gone. A person can filter by status, open a run's detail and act on runs that need them. It renders as a drawer, sidebar section or page.\n\n### Props\n- `runs`: Array<{ id: string; title: string; agent?: string; startedAt: Date; endedAt?: Date; status: \"queued\" | \"running\" | \"needs-input\" | \"succeeded\" | \"failed\" | \"cancelled\"; progress?: { done: number; total?: number }; summary?: string; error?: string }>, server-owned run objects.\n- `filter`: \"all\" | \"active\" | \"needs-input\" | \"failed\" | \"done\", controlled, with `onFilterChange(filter)`.\n- `selectedId?`: string and `onSelect(id | null)`: which run's detail is open.\n- `onOpenRun(id)`: navigate to the full run log or result.\n- `onCancel?(id)` and `onRetry?(id)`: actions for running and failed runs. The component never starts or stops work itself.\n- `connection`: \"live\" | \"reconnecting\" | \"offline\", how fresh the statuses are.\n- `lastUpdatedAt?`: Date, shown when not live.\n\n### States it must render\n- List: newest first, each row showing title, status label with icon, elapsed or finished time, and a one-line outcome (\"Tagged 412 tickets\", \"Failed: rate limited\").\n- Running row: progress such as \"120 of 400\" or an indeterminate indicator, with Cancel.\n- Needs input: amber row pinned to the top with \"Waiting for you\" and an Open action.\n- Failed row: the error in plain words with Retry and Open log.\n- Empty: \"No runs yet. Work you start in the background will appear here.\" Filter-empty says which filter is hiding runs.\n- Stale: when `connection` is not live, a banner \"Statuses may be out of date, last updated 09:14\" over an unchanged list.\n\n### Interactions\n- Status filter chips show a count each and call `onFilterChange`.\n- Clicking a row calls `onSelect` and expands the summary inline; \"Open\" calls `onOpenRun`.\n- Cancel asks for confirmation inline (\"Cancel this run? Work done so far is kept\") before `onCancel`.\n- A collapsed tray shows a badge with the number of active and needs-input runs, so it is reachable from anywhere.\n\n### Edge cases\n- A run finishes while its toast is dismissed or the page was closed: it is still in the list with its terminal state, since the tray reads runs, not notifications.\n- A run stuck in running far past its expected time: show \"No update for 20 min\" rather than a forever spinner.\n- Many finished runs: group older ones under \"Earlier\" and paginate; never drop them silently.\n- A run that transitions while selected: keep it selected and update in place without reordering under the pointer.\n\n## Must avoid\nA notification-only model where a dismissed toast is the only record the work ever happened.\n\n## Engineering constraint\nModel runs as first-class server objects with terminal states; poll or stream status into the tray.\n\n## Accessibility\n- The list is a `list` of runs whose accessible names include title and status; announce only terminal transitions and needs-input, not progress ticks.\n- The collapsed badge is a button named like \"Background runs: 2 running, 1 needs you\".\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "silent-enrichment-with-reveal",
      "name": "Silent enrichment with reveal",
      "family": "Dark matter",
      "component": "EnrichedRecord",
      "prompt": "# Build the \"Silent enrichment with reveal\" pattern: `EnrichedRecord`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nAI-added fields and tags applied automatically, marked so users can see and undo the machine's edit.\n\nUse it when: Data entry, CRM hygiene, tagging and triage where the win is nobody having to ask.\n\n## What to build\nA record view (contact, ticket, row) where fields the AI filled in automatically are marked, each with its source and time on reveal and a one-click undo. Values a person entered are never overwritten; the AI's disagreement appears as a suggestion beside them. It renders as a record detail panel or form.\n\n### Props\n- `fields`: Array<{ key: string; label: string; value: string | null; origin: \"person\" | \"ai\" | \"import\"; provenance?: { source: string; sourceUrl?: string; at: Date; confidence?: number; previousValue?: string | null }; suggestion?: { value: string; source: string } }>, values with per-field provenance.\n- `revealedKey?`: string and `onReveal(key | null)`: which field's provenance is open.\n- `onUndo(key)`: revert an AI-filled field to its previous value.\n- `onAcceptSuggestion(key)` and `onDismissSuggestion(key)`: decide on a suggestion shown beside a person-entered value.\n- `onEdit(key, value)`: a person edits a field, which turns its origin to \"person\".\n- `showOnlyAi?`: boolean, filter to AI-filled fields for review. The component never writes values itself.\n\n### States it must render\n- Default: every field shows its value; AI-filled fields carry a small AI mark and lighter label, \"Filled by AI\".\n- Revealed: the field shows source, time, confidence and the previous value (\"was empty\"), with Undo and a link to the source.\n- Suggestion beside a person value: the person's value stays as is, with \"AI suggests 25 (from billing)\" and Accept / Dismiss.\n- Undone: the field returns to its previous value with \"AI value removed\" and an option to restore it until the panel closes.\n- Review filter: only AI-filled fields listed with a count, e.g. \"6 fields filled by AI today\".\n\n### Interactions\n- Clicking the AI mark calls `onReveal`; clicking it again or pressing Escape closes it.\n- Undo calls `onUndo` and keeps focus on the field.\n- Editing an AI-filled field drops the AI mark and records it as the person's value via `onEdit`.\n- Accepting a suggestion calls `onAcceptSuggestion` and then marks the field as AI-filled with the person's acceptance noted.\n\n### Edge cases\n- AI data conflicts with a person-entered value: never replace it; render a suggestion, as above.\n- Provenance missing for an AI value: mark it \"Filled by AI, source unknown\" rather than hiding the mark.\n- A field re-enriched several times: the reveal shows the latest source and offers \"History\" if more than one change exists.\n- Low-confidence values: show them as suggestions in empty fields rather than applied values when below the threshold you pass in.\n\n## Must avoid\nOverwriting user-entered values with model guesses and leaving no trace of what changed.\n\n## Engineering constraint\nStore provenance per field with the source and timestamp; never overwrite human values without a diff.\n\n## Accessibility\n- The AI mark is a button named \"Filled by AI from <source>, show details\"; the field's accessible description includes \"filled by AI\".\n- After Undo, announce \"<label> reverted to <previous value>\".\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "ambient-triage",
      "name": "Ambient triage",
      "family": "Dark matter",
      "component": "TriagedQueue",
      "prompt": "# Build the \"Ambient triage\" pattern: `TriagedQueue`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nIncoming work sorted, routed or drafted before the user arrives, with the reasoning available on tap.\n\nUse it when: High-volume queues: support inboxes, alerts, leads, code review.\n\n## What to build\nA work queue that the agent has already sorted into groups (needs you, drafted, routed elsewhere) before the person arrives. Each item can reveal the rule that placed it and be moved back, and the original arrival order is always one toggle away. It renders as the main list of an inbox, alert feed or review queue.\n\n### Props\n- `items`: Array<{ id: string; title: string; from?: string; receivedAt: Date; arrivalIndex: number; groupId: string; rule: { id: string; summary: string; detail?: string }; signals?: string[]; confidence?: number; movedBy?: \"person\" }>, the queue with the rule that fired for each item.\n- `groups`: Array<{ id: string; label: string; tone: \"needs-person\" | \"ai\" | \"neutral\"; destination?: string }>, the buckets in display order, e.g. \"Routed to Billing\".\n- `order`: \"triaged\" | \"arrival\", controlled; arrival order lists every item by `arrivalIndex`, ignoring groups.\n- `selectedId?`: string, the item whose reasoning is open.\n- `triagedAt?`: Date and `status`: \"ready\" | \"triaging\" | \"error\", for the header line, e.g. \"38 new overnight, sorted at 07:02\".\n- `onOrderChange(order)`, `onSelect(id | null)`: callbacks.\n- `onMove(id: string, toGroupId: string)`: the person overrides the placement. The component never re-routes items itself.\n- `onRuleFeedback?(ruleId: string, verdict: \"keep\" | \"stop\")`: lets the person correct the rule, not just the one item.\n\n### States it must render\n- Triaged: items grouped under labelled headers with counts; each row shows a short rule chip such as \"Billing keyword + invoice attached\".\n- Reasoning open: the selected item shows the full rule, the signals it matched, confidence, and a \"Move to\" control listing the other groups.\n- Arrival order: a flat list in the order work arrived, with each row still showing which group it was sent to, proving nothing was hidden.\n- Moved: an item the person re-placed shows \"Moved by you\" instead of the rule chip, and the old group count drops.\n- Triaging / error: while sorting, items appear in arrival order with a quiet \"Sorting\" note; on error, the arrival list shows with \"Not sorted: showing arrival order\", never an empty queue.\n- Empty group: the header stays with \"Nothing here\" so the person knows the bucket exists.\n\n### Interactions\n- Clicking a rule chip or row calls `onSelect` and opens the reasoning beside (or below at narrow widths) the list.\n- \"Move to\" calls `onMove`; afterwards offer \"Stop applying this rule\" and \"Keep the rule\", which call `onRuleFeedback`.\n- The order toggle is a two-option segmented control calling `onOrderChange`; selection and scroll position are kept across the switch.\n- Groups routed away from the person (to another team) can be collapsed but always show their count.\n\n### Edge cases\n- An item with no rule (arrived after triage ran): place it in a \"Not sorted yet\" group, never in a guessed one.\n- Low confidence placements (below a threshold you pass in): mark the chip \"Unsure\" and sort them first in their group.\n- Hundreds of items in one group: show the first 20 with \"Show all n\", keeping the count accurate.\n- The same rule routed many items: the reasoning view says how many items that rule moved this run.\n\n## Must avoid\nSilent routing with no override, so the user cannot learn or correct the rule that hid their work.\n\n## Engineering constraint\nKeep the pre-triage ordering recoverable and expose the routing rule that fired.\n\n## Accessibility\n- Each group is a labelled list (`aria-labelledby` its header) with its count in the label; the rule chip is a button named \"Why is this here: <rule summary>\".\n- After a move, announce \"Moved to <group>\" and keep focus on the same item in its new place.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "scheduled-agent-runs",
      "name": "Scheduled agent runs",
      "family": "Dark matter",
      "component": "ScheduledRunList",
      "prompt": "# Build the \"Scheduled agent runs\" pattern: `ScheduledRunList`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nRecurring autonomous work on a visible schedule, with results delivered into an existing surface.\n\nUse it when: Monitoring, reporting and maintenance jobs where the value is in never being asked for.\n\n## What to build\nA 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.\n\n### Props\n- `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 }>.\n- `selectedId?`: string and `onSelect(id | null)`.\n- `onRunNow(id)`, `onPause(id)`, `onResume(id)`, `onEnd(id)` and `onEdit(id)`: callbacks. The component never runs or schedules work itself.\n- `autoPausePolicy?`: string, shown in each row, e.g. \"Pauses after 2 unopened results\".\n- `emptyAction?`: { label: string; onClick: () => void }, for creating the first schedule.\n\n### States it must render\n- Active: title, cadence (\"Every Monday at 08:00\"), destination, \"Last run Mon 28 Sep, opened by 4\" and \"Next Mon 5 Oct, 08:00\".\n- Running: \"Running now\" with the destination it will land in; the next-run time stays visible.\n- 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.\n- Paused / ended: muted row with who paused it or the end date; ended schedules move to an \"Ended\" section.\n- Empty: a short explanation of what schedules do with the create action.\n\n### Interactions\n- Run now calls `onRunNow` and the row shows Running; on delivery it shows \"Delivered just now\" with a link to the destination.\n- Pause is one click from the row, never only inside an edit form.\n- Resume on an auto-paused schedule asks for an end date or condition first (optional), so it does not run indefinitely again.\n- Selecting a row expands the full instruction, end condition and cost per run.\n\n### Edge cases\n- A schedule with no end date or condition: show \"No end date\" as a visible attribute, not blank space.\n- Last result delivered but not opened: show \"Not opened\" in the last-run line before auto-pause triggers.\n- The next run falls inside a pause window or after `endsAt`: show \"No more runs\" instead of a date.\n- Time zones: show times in the viewer's zone and include the zone when the schedule's own zone differs.\n\n## Must avoid\nSchedules that keep firing after the reason expired, producing output nobody reads and cost nobody sees.\n\n## Engineering constraint\nShow last-run and next-run; auto-pause on repeated unopened results or repeated failure.\n\n## Accessibility\n- 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.\n- After Run now, announce \"Running\" and then \"Delivered to <destination>\".\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "quiet-failure-surfacing",
      "name": "Quiet failure surfacing",
      "family": "Dark matter",
      "component": "PipelineFailureNotice",
      "prompt": "# Build the \"Quiet failure surfacing\" pattern: `PipelineFailureNotice`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA background failure becomes a visible, actionable state rather than an absence of output.\n\nUse it when: Any invisible pipeline the user would otherwise assume had succeeded.\n\n## What to build\nA visible failure state for a background pipeline (sync, enrichment, scheduled job) that would otherwise just produce nothing. It says what failed, what is missing because of it, and what the person can do, with a capped retry. It renders in place of, or above, the results the pipeline feeds.\n\n### Props\n- `source`: { name: string; lastSuccessAt?: Date }, the pipeline or connector, e.g. \"Billing sync\".\n- `status`: \"ok\" | \"empty\" | \"failed\" | \"retrying\" | \"retry-exhausted\" | \"recovering\", controlled; `empty` and `failed` are distinct values from the API, never inferred from a zero count.\n- `error?`: { message: string; cause: \"transient\" | \"auth\" | \"config\" | \"unknown\"; at: Date }, the plain-language reason.\n- `impact?`: string, what downstream output is missing or stale, e.g. \"64 accounts are missing from this list; next week's report is on hold\".\n- `retry`: { attempt: number; max: number }, the visible ceiling.\n- `onRetry()`, `onFix?()` (reconnect, open settings) and `onDismiss?()`: callbacks. The component never retries on its own.\n- `fixLabel?`: string, the label for the real fix, e.g. \"Reconnect billing\".\n\n### States it must render\n- Empty (genuine): \"No results. Billing sync last succeeded at 06:00\", so a true zero is stated with its freshness.\n- Failed: a red notice naming the source, the time, the reason and the impact, with Retry and the fix action; any partial results below are marked as incomplete.\n- Retrying: \"Retrying, attempt 2 of 3\" with buttons disabled.\n- Retry exhausted: \"Retrying won't fix this\" with the cause (e.g. an expired token), Retry removed, and the fix action made primary.\n- Recovering: \"Reconnected, syncing\" with progress; then the notice clears and newly loaded items are briefly marked as \"Was missing\".\n\n### Interactions\n- Retry calls `onRetry` and is disabled once `attempt` reaches `max`.\n- When `error.cause` is \"auth\" or \"config\", the fix action is primary from the start and Retry is secondary, because retrying cannot succeed.\n- Dismiss collapses the notice to a persistent \"1 source failing\" chip, never removes it while the failure lasts.\n\n### Edge cases\n- Partial failure (some records loaded): show the results with an \"Incomplete\" header and the count missing, not a clean list.\n- `lastSuccessAt` is missing: say \"Has never synced successfully\".\n- Several failing sources: stack one line per source with a single summary header, not several full banners.\n- Failure that resolves while the notice is open: switch to Recovering rather than vanishing, so the person sees it was fixed.\n\n## Must avoid\nDegrading to empty results, so the user reads a broken pipeline as \"nothing found\".\n\n## Engineering constraint\nDistinguish empty-result from error at the API level; retries need a user-visible ceiling.\n\n## Accessibility\n- The failed and retry-exhausted states use `role=\"alert\"` once on entry; retry progress is polite.\n- The notice is labelled by the source name so a screen-reader user can tell which pipeline broke.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "ai-made-label",
      "name": "AI-made label",
      "family": "Identifiers",
      "component": "AiContentLabel",
      "prompt": "# Build the \"AI-made label\" pattern: `AiContentLabel`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA compact badge marking content as generated or assisted, at the point of consumption.\n\nUse it when: Public content, marketplaces, and anywhere provenance affects how a viewer should read it.\n\n## What to build\nA small badge on a piece of content stating how much of it AI produced and whether a person reviewed it, using a graded scale instead of a single \"AI\" mark. Opening it explains the specifics in a sentence or two. It renders next to titles, bylines, thumbnails or posts wherever the content is consumed.\n\n### Props\n- `degree`: \"ai-generated\" | \"ai-drafted\" | \"ai-assisted\" | \"ai-edited\", the gradient, mapped to labels such as \"AI-generated\", \"AI-drafted, reviewed\", \"AI-assisted\".\n- `review`: { status: \"reviewed\" | \"not-reviewed\"; by?: string; at?: Date; changedShare?: number }, human oversight, e.g. \"rewrote 32%\".\n- `detail?`: string, how the AI was used, e.g. \"Drafted from 14 resolved support tickets\".\n- `model?`: string, optional model or tool provenance.\n- `size`: \"inline\" | \"compact\", and `labels?` for the degree names.\n- `learnMoreHref?`: string, a link to the policy explaining the scale.\n- `onOpen?()`: callback when the detail is opened, for analytics.\n\n### States it must render\n- Collapsed: icon plus degree label, e.g. \"AI-drafted, reviewed\"; unreviewed AI-generated content reads \"AI-generated, not reviewed\".\n- Open: a popover with the degree in words, how AI was used, who reviewed it and when, and what share they changed.\n- Not reviewed: the open state says so plainly with a caution, e.g. \"Not reviewed by a person yet. Steps may be wrong\".\n- Light assistance: \"AI-assisted\" with a lighter treatment and detail like \"Written by a person; AI suggested a table\".\n\n### Interactions\n- Clicking or pressing Enter opens the popover; Escape or clicking outside closes it.\n- The learn-more link opens the policy explaining every degree on the scale.\n- In lists, each label opens independently; opening one closes any other.\n\n### Edge cases\n- `degree` set but `review` unknown: show \"review status unknown\" rather than implying review.\n- Compact size on thumbnails: keep the degree distinguishable (different icon or short code), not one shared \"AI\" glyph.\n- Content that changed after review: show \"Changed since review\" if the parent passes it.\n- No AI involvement: render nothing; do not show a \"Human-made\" claim unless the parent explicitly passes one.\n\n## Must avoid\nBinary \"AI\" labels that flatten heavy generation and light assistance into one meaningless mark.\n\n## Engineering constraint\nC2PA's ai-disclosure assertion carries model provenance and degree of human oversight — use the gradient.\n\n## Accessibility\n- The badge is a button with a full accessible name such as \"AI-drafted, reviewed by a person. Show details\".\n- Degrees differ by text and icon, never only by colour or opacity.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "content-credentials-panel",
      "name": "Content credentials panel",
      "family": "Identifiers",
      "component": "ContentCredentials",
      "prompt": "# Build the \"Content credentials panel\" pattern: `ContentCredentials`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nAn expandable manifest: who made this, with which tools, and what changed since capture.\n\nUse it when: Media pipelines, journalism, evidence, and EU AI Act Article 50 exposure from August 2026.\n\n## What to build\nAn expandable provenance panel attached to an image or media file: who made it, with which tools, who signed it and what changed since capture, including any AI-edited region. When no credentials are present it says the origin is unknown, not that the file is fake. It renders as a pin on the media that opens a card, with a link to the full history.\n\n### Props\n- `manifest`: { status: \"verified\" | \"invalid\" | \"none\"; signer?: string; issuedAt?: Date; creator?: string; tools?: string[]; aiUsed?: { kind: \"generated\" | \"edited\"; region?: { x: number; y: number; w: number; h: number } }; history?: Array<{ action: string; tool?: string; at: Date }>; ingredients?: Array<{ title: string; status: \"verified\" | \"none\" }> } | null.\n- `mediaLabel`: string, describes the asset, e.g. \"Photo of a shop front\".\n- `watermark?`: { detected: boolean; provider?: string }, a durable signal checked separately.\n- `open`: boolean and `onOpenChange(open)`, controlled.\n- `showRegion`: boolean and `onShowRegionChange(show)`, overlay the AI-edited area on the media.\n- `onInspect?()`: open a full verification view. The component only displays verification results; it never signs or verifies.\n\n### States it must render\n- Verified: a \"CR\" pin; the card lists creator, signer, date, tools and an AI line such as \"AI-edited: background extended\".\n- Full history: an ordered list of actions since capture, with ingredients and their own credential status.\n- AI region shown: the edited area outlined on the media with \"AI-edited area\" and a toggle to hide it.\n- No credentials: neutral \"No Content Credentials. Origin unknown\" with a note that metadata is often stripped on upload or re-encode, and the watermark result if available.\n- Invalid: amber \"Credentials don't match this file. It may have been altered after signing\", distinct from none.\n\n### Interactions\n- Clicking the pin calls `onOpenChange(true)`; Escape or clicking outside closes it and returns focus to the pin.\n- \"Show AI-edited area\" toggles `onShowRegionChange`; it is only present when `aiUsed.region` exists.\n- \"Full history\" expands the history list inside the card; \"Inspect\" calls `onInspect`.\n\n### Edge cases\n- `manifest` is null or status none: never use red, warning or \"fake\" language.\n- Watermark detected but no manifest: say \"No credentials, but a watermark from <provider> indicates AI generation\".\n- Ingredients with missing credentials: list them as \"unknown origin\" without downgrading the verified parent.\n- Small thumbnails: the pin stays tappable (at least 24 px) and the card opens in a sheet at narrow widths.\n\n## Must avoid\nTreating an unsigned asset as proof of fakery — manifests strip on re-encode all the time.\n\n## Engineering constraint\nPair signed manifests with watermarking; audit CDN and CMS so uploads do not strip metadata.\n\n## Accessibility\n- The pin is a button named \"Content Credentials: verified\" (or \"none\", \"invalid\"); the card is a dialog labelled with the media label.\n- When the AI region is shown, describe it in text as well, e.g. \"AI-edited area: right third of the image\".\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "edit-attribution-history",
      "name": "Edit attribution history",
      "family": "Identifiers",
      "component": "EditHistory",
      "prompt": "# Build the \"Edit attribution history\" pattern: `EditHistory`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA per-change record of which edits came from a human and which from a model.\n\nUse it when: Collaborative documents, code and designs where authorship affects review.\n\n## What to build\nA per-change history for a document, file or design that attributes each edit to the person or agent who made it, shows exactly what each change touched, and lets a single change be reverted without undoing the others. It renders as a side panel next to the content.\n\n### Props\n- `changes`: Array<{ id: string; actor: { kind: \"person\" | \"agent\"; name: string; onBehalfOf?: string }; at: Date; summary: string; ranges: Array<{ before: string; after: string; location?: string }>; revertible: boolean; revertedBy?: { name: string; at: Date } }>, one entry per edit, never batched across actors.\n- `selectedId?`: string and `onSelect(id | null)`: which change is highlighted in the content.\n- `filter`: \"all\" | \"people\" | \"agents\" | string (an actor id), with `onFilterChange`.\n- `onRevert(id)`: revert only this change. The component never edits the document itself.\n- `groupBy?`: \"none\" | \"session\", collapse an actor's consecutive edits into an expandable group that still lists each one.\n- `conflicts?`: Record<string, string>, reasons a change cannot be reverted cleanly, keyed by change id.\n\n### States it must render\n- List: newest first, each entry with the actor chip (agents visibly distinct), time and a specific summary like \"Changed refund window from 5 to 10 days\".\n- Selected: the change's before and after text inline, with the affected words highlighted in the content.\n- Grouped: \"Agent made 50 edits\" expands to all 50, each still selectable and revertible.\n- Reverted: the entry stays, struck through, with \"Reverted by <name>\".\n- Conflict: Revert disabled with the reason, e.g. \"Later edits changed this text\".\n\n### Interactions\n- Clicking an entry calls `onSelect`; arrow keys move between entries.\n- Revert asks for inline confirmation showing the before text, then calls `onRevert`.\n- Filter chips by people, agents or a specific actor call `onFilterChange` and show counts.\n\n### Edge cases\n- An agent edit made with a person's credentials: show the agent as actor and the person as \"on behalf of\"; if unknown, label it \"Actor unclear\" rather than crediting the person.\n- A change that touched many places: show the first three ranges and \"+ n more\".\n- Reverting a change that a later change depends on: surface the conflict, never silently revert both.\n- Pure formatting or whitespace edits: label them as such so they don't look like content changes.\n\n## Must avoid\nCollapsing an agent's fifty edits into one anonymous \"updated\" entry.\n\n## Engineering constraint\nAttribute at commit or block level with the agent identity, not the human's credentials.\n\n## Accessibility\n- The history is a list; each entry's name includes actor type, actor, time and summary.\n- After revert, announce \"Reverted: <summary>\" and keep focus on that entry.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "agent-identity-chips",
      "name": "Agent identity chips",
      "family": "Identifiers",
      "component": "ActorChip",
      "prompt": "# Build the \"Agent identity chips\" pattern: `ActorChip`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nDistinct visual identity for each acting agent, separate from the human who deployed it.\n\nUse it when: Multi-agent products and shared workspaces where several actors write to the same surface.\n\n## What to build\nA compact chip that names who performed an action: a person or a specific agent, with the agent visibly distinct and linked to the person who deployed it. Opening it shows the agent's scopes and recent actions. It renders inline in activity feeds, comments, commit lists and assignee fields.\n\n### Props\n- `actor`: { kind: \"person\" | \"agent\"; id: string; name: string; avatarUrl?: string; role?: string }, the principal that actually acted.\n- `onBehalfOf?`: { id: string; name: string }, the person who deployed or is responsible for the agent; required when `kind` is \"agent\".\n- `scopes?`: string[], what the agent is allowed to do, e.g. \"Read tickets\", \"Post replies\".\n- `recentActions?`: Array<{ summary: string; at: Date }>, for the detail popover.\n- `size`: \"sm\" | \"md\", and `showDeployer?`: boolean (default true for agents).\n- `status?`: \"active\" | \"paused\" | \"revoked\", the agent's current standing.\n- `onOpenProfile?(id)` and `onOpenLog?(id)`: callbacks to navigate to the actor's profile or full audit log.\n\n### States it must render\n- Person: avatar or initials plus name, plain styling.\n- Agent: an agent glyph or distinct shape (not a person avatar), its own name, an \"Agent\" label, and \"for <person>\" in smaller text.\n- Detail open: a popover listing role, deployer, scopes, status and the last few actions, with \"View full log\".\n- Paused or revoked agent: the chip shows the status in text and muted styling; past actions keep the chip so history stays attributable.\n- Unknown actor: \"Unknown actor\" with a warning icon, never falling back to the deployer's name.\n\n### Interactions\n- Clicking or pressing Enter on the chip opens the detail popover; Escape closes it and returns focus to the chip.\n- \"View full log\" calls `onOpenLog`; the actor's name in the popover calls `onOpenProfile`.\n- The deployer name inside the popover is its own link, so the human and the agent are navigable separately.\n\n### Edge cases\n- An agent acting for a team rather than a person: `onBehalfOf` shows the team name.\n- Several agents with similar names: show a distinguishing role or short id in the chip at `md` size.\n- Small sizes in dense feeds: the agent marker (shape or glyph) remains even when the name truncates, and the full name is in the tooltip and accessible name.\n- A person and their agent acting in the same thread: chips must never look identical.\n\n## Must avoid\nAgents posting as the user, making accountability impossible to reconstruct after an incident.\n\n## Engineering constraint\nGive agents real principals in your identity system with scoped credentials and audit trails.\n\n## Accessibility\n- The chip is a button whose name reads \"<agent name>, agent acting for <person>\" or \"<person name>\".\n- The popover is a non-modal dialog labelled by the actor name.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "synthetic-media-disclosure",
      "name": "Synthetic media disclosure",
      "family": "Identifiers",
      "component": "SyntheticMediaBadge",
      "prompt": "# Build the \"Synthetic media disclosure\" pattern: `SyntheticMediaBadge`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nLayered disclosure on generated media: visible mark, embedded watermark, signed metadata.\n\nUse it when: Image, video and voice generation, especially anything depicting real people or events.\n\n## What to build\nA disclosure overlay for generated images, video or audio that shows all three layers (visible label, signed metadata and embedded watermark) and which ones survived the file's journey. Opening it explains what each layer confirms. It renders on the media in viewers, upload previews and moderation tools.\n\n### Props\n- `media`: { kind: \"image\" | \"video\" | \"audio\"; description: string }, what the asset is.\n- `layers`: { visibleLabel: \"present\" | \"missing\"; metadata: { status: \"intact\" | \"stripped\" | \"invalid\"; generator?: string; createdAt?: Date; edits?: string[] }; watermark: { status: \"detected\" | \"not-detected\" | \"not-checked\"; provider?: string; confidence?: \"high\" | \"medium\" | \"low\" } }, verification results from the intake check.\n- `depictsRealPerson?`: boolean, raises emphasis on the disclosure.\n- `placement`: \"overlay\" | \"below\", where the visible label renders; use \"below\" for audio.\n- `open`: boolean and `onOpenChange(open)`.\n- `onShowWatermark?()`: reveal where the watermark sits, if the detector supports it. The component never embeds or detects anything itself.\n\n### States it must render\n- All layers intact: a visible \"AI-generated\" label; the panel says \"Confirmed by all 3 layers\" with generator and date.\n- Label lost (cropped): the panel notes \"Visible label missing\" while metadata and watermark still confirm.\n- Metadata stripped (re-encoded or screenshotted): \"Metadata removed\"; the watermark alone confirms, with its confidence.\n- Only watermark, low confidence: \"Possibly AI-generated\" with the confidence stated, not a definitive claim.\n- No signal: \"No AI signals found\", with a note that this does not prove the media is authentic.\n\n### Interactions\n- Clicking the label opens the panel; the overall verdict is re-rendered as a visible label even when the original corner mark was lost.\n- Each layer row expands to show what it records (generator, edits, provider).\n- \"Show where the watermark is\" calls `onShowWatermark`.\n\n### Edge cases\n- Layers disagree (metadata says edited, watermark not detected): list both results; never pick one silently.\n- Audio: the visible label renders as text by the player and is read before playback controls.\n- Media depicting a real person: the label is always shown, never collapsed to an icon.\n- Watermark \"not-checked\": say so, rather than implying it was absent.\n\n## Must avoid\nRelying on a corner watermark that a crop removes, with no durable signal underneath.\n\n## Engineering constraint\nProvenance is rich but fragile, watermarks sparse but durable — ship both, verify at intake.\n\n## Accessibility\n- The verdict is part of the media's accessible description, e.g. \"AI-generated image, confirmed by watermark\".\n- Layer rows use text status (\"present\", \"missing\") alongside icons.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "ai-identity-name-and-persona",
      "name": "AI identity: name and persona",
      "family": "Identifiers",
      "component": "PersonaConfigurator",
      "prompt": "# Build the \"AI identity: name and persona\" pattern: `PersonaConfigurator`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA deliberate name, voice and character for the AI, setting expectations before any output arrives.\n\nUse it when: Any assistant users will address directly and repeatedly.\n\n## What to build\nAn admin panel for setting the assistant's name, label and voice, with a live preview of sample replies and a check that blocks persona choices that overclaim. The persona is edited and versioned separately from task instructions. It renders in product or workspace settings.\n\n### Props\n- `persona`: { name: string; label: string; avatarUrl?: string; tone: string; description: string; version: number }, the current draft; `label` is the always-visible disclosure, e.g. \"AI agent\".\n- `toneOptions`: Array<{ id: string; label: string; description: string }>, the voices on offer, e.g. Plain, Warm.\n- `previews`: Array<{ prompt: string; reply: string }>, sample exchanges rendered in the current voice (supplied by the parent).\n- `issues`: Array<{ id: string; severity: \"block\" | \"warn\"; message: string; excerpt?: string }>, results of the overclaim check, e.g. claims of feelings, being human or guaranteed outcomes.\n- `publishedVersion?`: number and `status`: \"draft\" | \"checking\" | \"publishing\" | \"published\" | \"error\".\n- `onChange(persona)`, `onPublish()` and `onRevert(version: number)`: callbacks. The component never generates text itself.\n\n### States it must render\n- Draft: name, label, tone and description fields, with a preview showing how the name and label appear in a chat header plus sample replies.\n- Checking: preview dimmed with \"Checking wording\"; Publish disabled.\n- Blocked: issues listed above the Publish button with the offending excerpt highlighted in the preview (e.g. \"I promise I'll fix this\", \"I'm so happy to help!\" as a feelings claim); Publish disabled until fixed.\n- Warnings only: Publish enabled, warnings visible with a short explanation each.\n- Published: \"Version 4 is live\" with the date and a Revert to previous version action.\n\n### Interactions\n- Changing tone or name calls `onChange`; the preview updates from the new `previews` prop.\n- Clicking an issue scrolls to and highlights the excerpt in the preview.\n- Publish calls `onPublish` only when there are no `block` issues and `label` is non-empty.\n- The version selector shows history and calls `onRevert` after a confirm.\n\n### Edge cases\n- Empty or removed AI label: blocking issue, since the persona must always disclose it is an AI.\n- A human-sounding name (a first name with no label): allowed, but the label must render next to it everywhere in the preview.\n- Unpublished draft differs from live: show \"Unpublished changes\" and a diff of changed fields.\n- Very long names: cap at a set length and show how it truncates in the chat header preview.\n\n## Must avoid\nA cute persona that overpromises competence, or claims of feeling that mislead about what it is.\n\n## Engineering constraint\nKeep persona in a versioned system prompt separate from task instructions so it can be tuned alone.\n\n## Accessibility\n- The issues list is announced when it changes after a check; each issue links to its excerpt with a focusable target.\n- Tone options are a radio group with their descriptions as accessible descriptions.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "ai-visual-language",
      "name": "AI visual language",
      "family": "Identifiers",
      "component": "AiMarker",
      "prompt": "# Build the \"AI visual language\" pattern: `AiMarker`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA consistent color, icon and motion vocabulary that marks AI surfaces as distinct from deterministic ones.\n\nUse it when: Products where AI features are woven through an existing interface.\n\n## What to build\nA small set of primitives (an inline marker, a surface wrapper and a legend) that apply the product's AI accent and icon only to things a model produced or is doing, and plain styling to rule-based automation. It enforces one documented rule for when the AI treatment applies. It renders across existing screens wherever AI and deterministic output sit side by side.\n\n### Props\n- `source`: \"model\" | \"rule\" | \"person\", required; only \"model\" receives the AI accent and icon, \"rule\" gets a neutral automation icon, \"person\" gets nothing.\n- `variant`: \"inline\" | \"surface\" | \"legend\", the chip beside a value, the frame around a generated block, or the key that explains the marks.\n- `state?`: \"idle\" | \"working\" | \"done\", for model surfaces only; \"working\" adds the AI motion cue.\n- `label?`: string, accessible and tooltip text, e.g. \"Written by AI\" or \"Set by rule: overdue > 30 days\".\n- `tokens?`: { accent: string; icon: ReactNode; motion: string }, the design tokens, so one definition drives every marker.\n- `highlight?`: \"model\" | \"rule\" | null, set by the legend to light up every marker of one source on screen.\n- `onHighlightChange?(source | null)`: callback from the legend.\n\n### States it must render\n- Model inline: AI icon plus accent tint on a small chip next to the value.\n- Model surface: a block framed with the accent edge and a header label \"Generated by AI\".\n- Model working: the same surface with a subtle motion cue (static under reduced motion) and \"Writing\".\n- Rule: a neutral icon (e.g. a cog) with \"Automatic\" text, no accent and no AI icon.\n- Legend: two keys (\"Written by AI\", \"Set by a rule\") that, when pressed, highlight the matching markers and dim the rest.\n\n### Interactions\n- Pressing a legend key calls `onHighlightChange`; pressing it again clears the highlight.\n- Hovering or focusing a marker shows its `label` as a tooltip.\n- Markers are not buttons unless they have an action; the legend keys are toggle buttons.\n\n### Edge cases\n- `source` omitted: a type error, not a default, so AI styling cannot be applied by accident.\n- A rule that calls a model internally: pass \"model\"; the rule is about how the value was produced, not the feature name.\n- Several AI blocks nested: only the outermost surface gets the frame; inner items use the inline marker.\n- Buttons that trigger AI (e.g. \"Summarise\"): use the AI icon only on that action, never across a whole toolbar.\n\n## Must avoid\nSparkles on every button until the signal is noise, or AI styling on features that are plain automation.\n\n## Engineering constraint\nDefine the AI accent and icon set as design tokens with a documented rule for when they apply.\n\n## Accessibility\n- Every marker exposes its source in text (visually hidden if needed), e.g. \"AI-written\", so the distinction does not rely on colour or icon alone.\n- Legend keys use `aria-pressed` and announce how many items are highlighted.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "rated-response-with-reason",
      "name": "Rated response with reason",
      "family": "Feedback & correction",
      "component": "ResponseRating",
      "prompt": "# Build the \"Rated response with reason\" pattern: `ResponseRating`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nThumbs plus a short structured reason, so the signal is diagnosable rather than a bare score.\n\nUse it when: Any generative surface you intend to evaluate and improve.\n\n## What to build\nThumbs up and down controls on an AI response that, after a rating, ask for a short structured reason (chips plus an optional note) and show exactly what will be sent. It turns a bare score into a diagnosable report. It renders in the action row under each response.\n\n### Props\n- `rating`: \"up\" | \"down\" | null, controlled.\n- `reasons`: { up: Array<{ id: string; label: string }>; down: Array<{ id: string; label: string }> }, e.g. \"Inaccurate\", \"Outdated\", \"Wrong source\", \"Too long\".\n- `selectedReasons`: string[] and `note`: string.\n- `status`: \"idle\" | \"asking\" | \"submitting\" | \"submitted\" | \"error\".\n- `contextSummary`: Array<{ label: string; value: string }>, what is attached to the rating, e.g. conversation, response id, model version, sources.\n- `onRate(rating)`, `onReasonsChange(ids)`, `onNoteChange(text)`, `onSubmit()` and `onSkip()`: callbacks. The component never sends anything itself.\n- `noteMaxLength?`: number (default 500).\n\n### States it must render\n- Idle: two icon buttons with labels in tooltips.\n- Asking: after a rating, a small panel with reason chips for that direction, an optional note, a \"What we'll send\" disclosure listing `contextSummary`, and Send / Skip.\n- Submitting: Send disabled with a busy indicator.\n- Submitted: \"Thanks, sent to the quality team\" with the chosen reasons echoed; the thumb stays pressed.\n- Error: \"Couldn't send\" with Retry, keeping the selections.\n\n### Interactions\n- Clicking a thumb calls `onRate` and opens the reason panel; clicking the pressed thumb again clears the rating.\n- Chips are multi-select toggles calling `onReasonsChange`.\n- Send calls `onSubmit`; Skip calls `onSkip`, which keeps the bare rating rather than discarding it.\n- Changing direction after submitting reopens the panel with the other reason set.\n\n### Edge cases\n- A down rating with no reason chosen: allow Send but nudge once (\"A reason helps us fix it\"), never block.\n- Note contains personal data: show a hint that it is attached to the conversation, and the context list makes clear what is shared.\n- Many responses in a thread: only one reason panel open at a time.\n- Ratings on a still-streaming response: thumbs disabled until done.\n\n## Must avoid\nThumbs with no follow-up, producing volume you cannot act on and users who stop bothering.\n\n## Engineering constraint\nLog the full request context with the rating; free-text reasons need clustering to be useful.\n\n## Accessibility\n- Thumbs are toggle buttons with `aria-pressed` and labels \"Good response\" / \"Bad response\".\n- When the reason panel opens, move focus to its first chip; on close, return focus to the thumb.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "approve-with-edits",
      "name": "Approve with edits",
      "family": "Feedback & correction",
      "component": "EditableProposal",
      "prompt": "# Build the \"Approve with edits\" pattern: `EditableProposal`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA diff of the agent's proposal the user can amend before accepting, rather than accept-or-discard.\n\nUse it when: Drafting and code flows where the output is 80% right and rejection wastes the work.\n\n## What to build\nA review view of the agent's proposed changes as separate hunks the person can accept, edit or drop individually before applying the whole set. The edited result is sent as a patch against the version the agent read, so nothing is regenerated. It renders inline in a chat, review panel or composer.\n\n### Props\n- `hunks`: Array<{ id: string; label?: string; before: string; after: string; decision: \"pending\" | \"accepted\" | \"edited\" | \"dropped\"; editedText?: string }>, the proposal split into reviewable changes.\n- `baseVersion`: string, the version id the agent read.\n- `currentVersion`: string, the latest version; a mismatch means the base is stale.\n- `status`: \"reviewing\" | \"applying\" | \"applied\" | \"conflict\" | \"error\", controlled.\n- `conflicts?`: Array<{ hunkId: string; reason: string }>, hunks that no longer apply cleanly.\n- `onDecide(hunkId, decision, editedText?)`: callback per hunk.\n- `onApply()`: apply all non-dropped hunks as they stand, and `onDiscard()`: drop the whole proposal. The component never applies changes itself.\n\n### States it must render\n- Reviewing: each hunk shows before and after with Accept, Edit and Drop; a footer says \"3 changes: 1 edited, 1 dropped\".\n- Editing a hunk: the after text becomes an editable field pre-filled with the agent's text; the original suggestion stays visible above it.\n- Applying: controls disabled with a busy indicator.\n- Applied: a receipt listing what went out as written, what was edited and what was dropped.\n- Conflict: hunks that clash with newer edits are marked with the reason and options to re-review against the latest version or drop them; clean hunks can still be applied.\n\n### Interactions\n- Accept, Edit and Drop call `onDecide`; a dropped hunk collapses to one line with Undo.\n- Apply is enabled once every hunk has a decision, or offers \"Apply and accept the rest\" when some are pending.\n- Keyboard: J and K move between hunks; A, E and D decide the focused hunk (shown in a hint).\n- Discard asks for confirmation if any hunk was edited, since that work would be lost.\n\n### Edge cases\n- `baseVersion` differs from `currentVersion`: show \"The text changed since this was proposed\" before the person starts reviewing.\n- All hunks dropped: Apply becomes \"Close\" and calls `onDiscard`.\n- An edited hunk is emptied: treat as a deletion and say so, rather than applying an empty string silently.\n- A single-hunk proposal: still offer Edit, never only Accept and Reject.\n\n## Must avoid\nAll-or-nothing accept buttons that force a full regeneration over one wrong sentence.\n\n## Engineering constraint\nApply as a patch against the version the agent read; detect and surface conflicts on stale bases.\n\n## Accessibility\n- Each hunk is a group labelled by its label or position (\"Change 2 of 3\") and decision; before and after are labelled in text, not just by colour.\n- After applying, focus moves to the receipt.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "regenerate-with-instruction",
      "name": "Regenerate with instruction",
      "family": "Feedback & correction",
      "component": "RegeneratePanel",
      "prompt": "# Build the \"Regenerate with instruction\" pattern: `RegeneratePanel`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nRe-run scoped to a correction — shorter, more formal, drop the third point — instead of a blind retry.\n\nUse it when: When the miss is directional and the user can name it in a few words.\n\n## What to build\nA control for re-running an AI output with a named correction (quick chips like \"Shorter\" or \"More formal\", point-level actions like keep or drop, and a free-text instruction) instead of a blind retry. Each result is kept as a numbered version for comparison. It renders under or beside a generated response or draft.\n\n### Props\n- `presets`: Array<{ id: string; label: string }>, quick instructions, e.g. Shorter, More formal, Friendlier.\n- `parts?`: Array<{ id: string; text: string; mark: \"keep\" | \"drop\" | \"neutral\" }>, the output's points or paragraphs, which the person can pin or drop.\n- `instruction`: string and `selectedPresetIds`: string[], controlled.\n- `versions`: Array<{ id: string; label: string; instruction?: string; createdAt: Date }> with `currentVersionId`.\n- `status`: \"idle\" | \"regenerating\" | \"error\".\n- `onInstructionChange(text)`, `onPresetsChange(ids)`, `onPartMark(id, mark)`: callbacks.\n- `onRegenerate({ instruction, presetIds, keep: string[], drop: string[], baseVersionId })`, `onSelectVersion(id)` and `onCancel()`: callbacks. The component never generates text itself.\n\n### States it must render\n- Idle: preset chips, an instruction field (\"Tell it what to change\"), and parts with keep or drop toggles; the button reads \"Rewrite\" only when an instruction, preset or mark is set.\n- Regenerating: the current version stays visible with \"Rewriting v1: more formal, drop point 3\" and Cancel.\n- New version: tabs or a switcher \"v1 | v2\" with each version's instruction shown under its tab; kept parts are marked as unchanged.\n- Error: the previous version remains current with \"Couldn't rewrite\" and Retry, the instruction preserved.\n\n### Interactions\n- Chips toggle and combine with free text; the composed instruction is shown as one line before submitting.\n- Marking a part \"keep\" pins it so it must return word for word; \"drop\" removes it; both go into `onRegenerate`.\n- Enter in the instruction field submits; Escape clears it.\n- Switching versions calls `onSelectVersion` without discarding any version.\n\n### Edge cases\n- No instruction at all: the plain retry is available but secondary and labelled \"Try again (no changes)\".\n- Contradicting presets (Shorter and Expand): selecting one deselects the other.\n- A kept part that came back changed: the parent can flag it and the version shows \"Kept part was altered\".\n- Many versions: show the latest five tabs with a \"More versions\" menu.\n\n## Must avoid\nA bare regenerate button that rolls the dice and loses the parts the user already liked.\n\n## Engineering constraint\nCarry the prior output into context as the base; keep both versions addressable for comparison.\n\n## Accessibility\n- Presets and keep/drop toggles use `aria-pressed`; version tabs follow the tabs pattern.\n- Announce \"v2 ready\" when a regeneration completes, without moving focus from the control.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "correction-memory",
      "name": "Correction memory",
      "family": "Feedback & correction",
      "component": "RuleCapturePrompt",
      "prompt": "# Build the \"Correction memory\" pattern: `RuleCapturePrompt`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA durable rule captured from a correction: never do that again, always use this term.\n\nUse it when: Repeat workflows where the same correction is otherwise made daily.\n\n## What to build\nA prompt that appears after a person corrects the AI, proposing to turn that correction into a standing rule written in plain, editable text with a clear scope. Nothing is remembered unless the person confirms, and saved rules can be listed, edited and deleted. It renders inline under the corrected output, with a companion list for managing rules.\n\n### Props\n- `correction`: { before: string; after: string }, what the person changed.\n- `proposedRule`: { text: string; scope: string }, the agent's suggested wording, e.g. \"Always write 'refund window', not 'return period'\", and scope, e.g. \"Billing replies\".\n- `scopeOptions`: Array<{ id: string; label: string }>, e.g. this reply only, this workflow, everyone on the team.\n- `status`: \"proposing\" | \"editing\" | \"saving\" | \"saved\" | \"declined\", controlled.\n- `rules?`: Array<{ id: string; text: string; scope: string; createdAt: Date; appliedCount?: number }>, existing rules for the management list.\n- `onSave(rule: { text: string; scope: string })`, `onDecline()`, `onEditRule(id, text)` and `onDeleteRule(id)`: callbacks. The component never stores a rule itself.\n\n### States it must render\n- Proposing: \"Remember this for next time?\" with the before and after, the rule text and scope, and Save rule / Just this once.\n- Editing: the rule text and scope become editable with a preview of how it reads.\n- Saved: \"Rule saved: applies to Billing replies\" with Undo and a link to all rules.\n- Declined: \"Kept to this reply only\"; the prompt closes and nothing is stored.\n- Rules list: each rule as text with scope, when it was added and how often it applied, with Edit and Delete; empty list says \"No saved rules\".\n\n### Interactions\n- Save calls `onSave` with the possibly edited text and scope; Just this once calls `onDecline`.\n- Ignoring the prompt (navigating away) counts as declined; no rule is inferred.\n- Undo after saving calls `onDeleteRule` for the new rule.\n- Delete in the list asks for inline confirmation.\n\n### Edge cases\n- A proposed rule broader than the correction (e.g. a single word change generalised to a style rule): default scope to the narrowest option.\n- A new rule that conflicts with an existing one: show both and ask which to keep before saving.\n- Duplicate rule: offer to update the existing one instead of adding another.\n- Empty rule text after editing: Save is disabled.\n\n## Must avoid\nSilently learning from a one-off correction and generalising it into behaviour nobody asked for.\n\n## Engineering constraint\nStore rules as reviewable text the user can edit and delete; never infer them without confirmation.\n\n## Accessibility\n- The prompt is a labelled region announced politely when it appears; it does not steal focus from the editor.\n- Scope options are a radio group; the saved confirmation is announced.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "report-and-escalate",
      "name": "Report and escalate",
      "family": "Feedback & correction",
      "component": "ReportResponse",
      "prompt": "# Build the \"Report and escalate\" pattern: `ReportResponse`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA clear route for harmful or high-stakes failures that reaches a human with the full context.\n\nUse it when: Regulated domains, safety-relevant output, and anything with reputational blast radius.\n\n## What to build\nA report control attached to each AI message that opens a short form (reason, severity, optional note), shows who will receive it and what context is attached, and confirms the hand-off to a human. Reporting never requires leaving the conversation or digging into settings. It renders in the message action menu, with the form as a popover or sheet.\n\n### Props\n- `reasons`: Array<{ id: string; label: string; severity: \"standard\" | \"urgent\" }>, e.g. \"Asked for sensitive data\", \"Harmful advice\", \"Wrong and costly\".\n- `selectedReasonId?`: string and `note`: string, controlled.\n- `attachedContext`: Array<{ label: string; value: string }>, e.g. run id, model version, retrieved sources, conversation excerpt, shown before sending.\n- `recipient`: { name: string; responseTime?: string }, who handles it, e.g. \"Trust and safety team, replies within 4 hours\".\n- `status`: \"closed\" | \"open\" | \"submitting\" | \"submitted\" | \"error\".\n- `receipt?`: { id: string; at: Date; nextStep: string }, shown after submitting.\n- `onOpen()`, `onReasonChange(id)`, `onNoteChange(text)`, `onSubmit()`, `onCancel()` and `onHideMessage?()`: callbacks. The component never sends reports itself.\n\n### States it must render\n- Closed: a visible \"Report\" action (flag icon plus text) on every AI message, not hidden in an overflow menu only.\n- Open: reason list, optional note, \"Goes to <recipient>\" and a \"Sent with your report\" list of attached context.\n- Urgent reason selected: the form shows that it will be escalated immediately and offers to hide the message now.\n- Submitted: a receipt with report id, time, who has it and what happens next, e.g. \"A person will review this within 4 hours\"; the message shows \"Reported\".\n- Error: \"Report not sent\" with Retry, keeping the reason and note.\n\n### Interactions\n- Report calls `onOpen`; the form opens next to the message without navigating away.\n- Selecting a reason calls `onReasonChange`; Send is enabled once a reason is chosen and calls `onSubmit`.\n- Hide message calls `onHideMessage` and replaces the content with \"Hidden: you reported this\" and a show option.\n- Cancel or Escape closes the form and returns focus to the Report button.\n\n### Edge cases\n- Missing run id or model version in `attachedContext`: show \"Some details couldn't be attached\" so triage knows, rather than failing silently.\n- Reporting the same message twice: show the existing receipt and offer to add a note.\n- The conversation contains sensitive data: the attached-context list states that the excerpt is included so the person can decide.\n- A high-stakes report while the AI conversation continues: the message stays reported and hidden state persists if the person scrolls away or reloads, driven by props.\n\n## Must avoid\nBurying reporting in settings so the only visible option is abandoning the product.\n\n## Engineering constraint\nAttach the run ID, model version and retrieval set to the report or triage cannot reproduce it.\n\n## Accessibility\n- The form is a dialog labelled \"Report this response\"; reasons are a radio group.\n- On submit, focus moves to the receipt and its next step is announced.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "branches",
      "name": "Branches",
      "family": "Feedback & correction",
      "component": "VersionTree",
      "prompt": "# Build the \"Branches\" pattern: `VersionTree`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nIteration that forks rather than overwrites, so earlier good states stay reachable.\n\nUse it when: Exploratory work where the fifth attempt is often worse than the second.\n\n## What to build\nA navigator for an iterative session modelled as a tree: every attempt is a node with a stable id, and retrying from an earlier version forks a new branch instead of overwriting later ones. A person can view, compare, continue from or link to any version. It renders as a compact version switcher with an expandable tree beside the output.\n\n### Props\n- `nodes`: Array<{ id: string; parentId: string | null; label: string; createdAt: Date; summary?: string; starred?: boolean; instruction?: string }>, the session tree.\n- `currentId`: string, the version shown in the main view.\n- `onSelect(id)`: show that version without changing anything.\n- `onBranchFrom(id)`: start a new attempt from this version; the parent adds the child node.\n- `onStar?(id, starred)`, `onCopyLink?(id)` and `onCompare?(aId, bId)`: callbacks.\n- `status`: \"idle\" | \"generating\", with `generatingFromId?` to show where a new node will appear.\n- `view`: \"switcher\" | \"tree\", controlled with `onViewChange`.\n\n### States it must render\n- Switcher: \"v2 of 5\" with previous and next among siblings, plus the branch's path (v1 > v2 > v5).\n- Tree: every version as a node with label, summary and time; the current one highlighted and starred ones marked.\n- Generating: a placeholder child node under the version being branched from, labelled \"New attempt\".\n- Viewing an earlier version: a banner \"You're viewing v2. Later versions v3 and v4 are kept\" with \"Try again from here\".\n- Single version: the switcher hides; the tree reads \"Only one version so far\".\n\n### Interactions\n- Selecting a node calls `onSelect`; it never deletes or reorders other nodes.\n- \"Try again from here\" calls `onBranchFrom` with the shown version; the new node appears as a sibling of later attempts, not a replacement.\n- Compare: choosing two nodes calls `onCompare`.\n- Copy link calls `onCopyLink` with the node's stable id.\n\n### Edge cases\n- Deep or wide trees: collapse branches not on the current path, showing \"+3 versions\".\n- A linked id that no longer exists: show \"This version isn't available\" and fall back to the latest on the same branch.\n- Branching while a generation is in progress: queue or disable \"Try again\" with the reason.\n- Nodes with identical summaries: always show the label and time so they stay distinguishable.\n\n## Must avoid\nA linear history where regenerating destroys the version the user actually preferred.\n\n## Engineering constraint\nModel the session as a tree, not a list; every node needs a stable, linkable ID.\n\n## Accessibility\n- The tree uses `role=\"tree\"` with `treeitem`s, arrow-key navigation and `aria-current` on the shown version.\n- Announce \"Branched from v2: new attempt v5\" when a new node arrives.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "variations",
      "name": "Variations",
      "family": "Feedback & correction",
      "component": "VariationPicker",
      "prompt": "# Build the \"Variations\" pattern: `VariationPicker`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nSeveral candidate outputs shown side by side so choosing replaces re-prompting.\n\nUse it when: Subjective output — images, copy, layouts, names — where recognition beats specification.\n\n## What to build\nA grid of several candidate outputs from one request, each a deliberately different direction, so a person picks the closest and refines it instead of rewriting the prompt. Earlier sets stay one click away, so a good candidate is never lost. It renders inline in a chat or in a generation panel.\n\n### Props\n- `sets`: Array<{ id: string; label: string; meta?: string; candidates: Array<{ id: string; preview: ReactNode; label: string; direction: string; seed?: string | number; parentId?: string }> }>, every set generated so far, newest last. `direction` says how a candidate differs, e.g. \"Type-led, bold\"; `parentId` records which candidate a varied set came from.\n- `activeSetId`: string and `onSetChange(id: string)`, controlled set navigation.\n- `selectedId?`: string and `onSelect(id: string | null)`, the candidate currently chosen for the next action.\n- `status`: \"generating\" | \"ready\" | \"varying\" | \"error\" | \"chosen\", controlled by the parent.\n- `expectedCount?`: number, tiles to reserve while generating (default 4).\n- `maxRounds?`: number, the cap on \"More like this\" rounds, with `roundsUsed`: number.\n- `chosen?`: { candidateId: string; setId: string; at: Date }, shown once a candidate is used.\n- `onVary(candidateId: string, strength: \"subtle\" | \"strong\")`, `onUse(candidateId: string)` and `onRetry()`: callbacks. The component never generates anything itself.\n\n### States it must render\n- Generating: `expectedCount` reserved tiles with skeletons and a line such as \"Generating 4 directions\"; no layout shift when results land.\n- Ready: each tile shows its preview, a letter, its label and its `direction` note; Use and More like this are visible but disabled until a tile is selected.\n- Selected: the chosen tile is outlined with a check mark and the actions name it (\"Use B\", \"More like B\").\n- Varying: a new set tab appears with reserved tiles labelled \"From B\"; earlier sets remain browsable.\n- Chosen: a one-line receipt (\"Used B from set 2\") with a link back to that set.\n- Error: tiles that did arrive stay usable; missing ones say \"Didn't generate\" with Retry.\n\n### Interactions\n- Clicking a tile selects it; clicking the selected tile again clears the selection.\n- More like this asks for subtle or strong variation, then calls `onVary` with the selected candidate.\n- Use calls `onUse` with the selected candidate; selecting a tile in an older set acts on that candidate, not on the latest set.\n- Arrow keys move between tiles (roving tabindex); Enter or Space selects.\n\n### Edge cases\n- Two candidates in one set share the same `direction`: show \"Similar to A\" on the later tile, so a weak set is visible instead of looking like four real choices.\n- Round cap reached: More like this is disabled with the reason (\"3 of 3 rounds used\").\n- Fewer candidates returned than expected: collapse the empty tiles and say \"3 of 4 returned\".\n- Text candidates (names, copy): previews clamp to equal height with an expand control so comparison stays side by side.\n\n## Must avoid\nFour near-identical candidates that cost four runs and give the user no real decision.\n\n## Engineering constraint\nDiversify with different seeds or sampling, not repeated calls; cache all candidates for later recall.\n\n## Accessibility\n- Tiles are buttons with `aria-pressed` and labels like \"Option B: Big date, type-led, bold\"; set tabs follow the tablist pattern.\n- Announce new sets once (\"Set 2 ready, 4 options\"), not each tile.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "plan-preview",
      "name": "Plan preview",
      "family": "Agent status & progress",
      "component": "PlanPreview",
      "prompt": "# Build the \"Plan preview\" pattern: `PlanPreview`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nThe intended sequence of steps, shown before execution and editable by the user.\n\nUse it when: Multi-step agent work where a wrong plan is expensive to discover halfway through.\n\n## What to build\nAn editable list of the steps an agent intends to take, shown before anything runs, with each step concrete enough to judge (the record, amount or file it touches) and labelled by what kind of effect it has. A person can remove optional steps or choose between variants, and the edited plan is exactly what runs. It renders in a chat reply or task panel ahead of a Run button.\n\n### Props\n- `plan`: { id: string; steps: Array<{ id: string; title: string; detail: string; effectLabel: string; required: boolean; included: boolean; variants?: Array<{ id: string; label: string }>; selectedVariantId?: string }> }, the same plan object the executor consumes.\n- `onEditStep(stepId: string, change: { included?: boolean; variantId?: string })`: the parent updates the plan and passes it back; the component holds no private copy.\n- `onReorder?(stepIds: string[])`: when reordering is allowed.\n- `estimateLabel?`: string, e.g. \"about 1 min\".\n- `status`: \"proposed\" | \"running\" | \"done\" | \"error\", controlled by the parent.\n- `stepResults?`: Record<string, { state: \"running\" | \"done\" | \"skipped\" | \"failed\"; detail: string }>.\n- `onRun(planId: string)` and `onRevise?(feedback: string)`: callbacks. The component never executes steps itself.\n\n### States it must render\n- Proposed: a badge \"Plan ready, nothing has run\", numbered steps each with a concrete title and detail (\"Change seats from 12 to 14: +$48 a month from today\") and an effect label (\"read only\", \"billing\", \"customer\", \"internal\").\n- Edited: removed steps are struck through, chosen variants are shown, and the footer reads \"4 steps, edited by you: charge card now, no customer email\".\n- Running: steps tick to done in the same list, with results matching what was shown; edits are locked.\n- Done: each step shows its result, edited steps are marked \"your edit\", and removed steps show \"Skipped, removed by you\".\n- Error: the failing step is marked and later steps show \"Not run\".\n\n### Interactions\n- Optional steps have a checkbox calling `onEditStep(id, { included })`; required steps have none and say why they are required.\n- Variant choices (for example \"Add to next invoice\" or \"Charge card now\") are a segmented control calling `onEditStep(id, { variantId })`.\n- Run calls `onRun(plan.id)` with the displayed plan; Revise opens a short text field and calls `onRevise`.\n- Keyboard reordering, when enabled, uses buttons \"Move up\" and \"Move down\" on each step.\n\n### Edge cases\n- Steps too abstract to judge (\"Update records\") should be refused upstream; if a step lacks detail, show \"No detail provided\" so the gap is visible.\n- Removing a step that later steps depend on: the parent marks dependents, and the component shows them as disabled with the reason.\n- The plan changes while the person is editing (agent revises it): highlight changed steps and keep the person's edits where step ids still match.\n- All optional steps removed: Run still works with the required steps and says how many remain.\n\n## Must avoid\nA plan generated for show that the executor ignores, or one too abstract to judge.\n\n## Engineering constraint\nSame plan object drives display and execution; edits must round-trip back into the run.\n\n## Accessibility\n- Steps are an ordered list; each checkbox and variant control is labelled with the step title.\n- Announce the edited summary after each change and the result after Run.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "live-step-stream",
      "name": "Live step stream",
      "family": "Agent status & progress",
      "component": "StepStream",
      "prompt": "# Build the \"Live step stream\" pattern: `StepStream`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA running log of steps and tool calls as they happen, collapsible to a one-line summary.\n\nUse it when: Long autonomous runs where silence reads as failure.\n\n## What to build\nA running log of an agent's steps and tool calls as they happen, each shown as one readable line with its result, collapsible to a one-line summary with step count and elapsed time. Warnings stand out, and raw tool output is available per step but hidden by default. It renders under a request in a chat or in a run panel, and stays readable after the run.\n\n### Props\n- `events`: Array<{ id: string; at: number; tool: string; text: string; result?: string; kind?: \"normal\" | \"warn\" | \"write\" | \"error\"; raw?: string }>, typed events appended by the parent from the stream.\n- `status`: \"starting\" | \"running\" | \"done\" | \"stopped\" | \"error\".\n- `elapsedSeconds`: number, supplied by the parent.\n- `collapsed`: boolean and `onCollapsedChange(v: boolean)`, controlled.\n- `summary?`: string, the one-line summary used when collapsed, e.g. \"Reading exit surveys\" or \"8 steps, 0:58, 1 warning\".\n- `onStop?()`: optional stop control while running.\n- `maxVisible?`: number, events kept rendered before older ones are grouped (default 50).\n\n### States it must render\n- Starting: \"Starting\" with \"0 steps, 0:00\".\n- Running: each event shows time, tool name and a plain sentence, e.g. \"Searched tickets mentioning cancel or downgrade since 1 Jul, 212 tickets\"; the newest line is marked live.\n- Warning: a warn event is amber with its consequence, e.g. \"Billing export is missing 1 to 3 July. Using invoices for those days instead\", and the collapsed summary counts it.\n- Collapsed: one line with current step, count and elapsed time.\n- Done: the live marker is removed and the header reads e.g. \"8 steps, 0:58\"; the log remains expandable and searchable.\n- Error or stopped: the last event is marked and the header says which step it ended on.\n\n### Interactions\n- The header toggles `collapsed`.\n- Each event with `raw` has a \"raw output\" toggle that reveals a monospaced block for that step only.\n- The view follows new events only if the reader is at the bottom; otherwise a \"Jump to latest\" button appears.\n- A text filter (after the run) narrows events by tool or text.\n\n### Edge cases\n- Raw output is never the default view; even when every event has raw output, lines show the readable sentence first.\n- Bursts of events: batch renders per animation frame and keep earlier lines from shifting.\n- Very long runs: group older events (\"Read 40 documents\") and keep warnings ungrouped.\n- Events arriving out of order: sort by `at`.\n\n## Must avoid\nRaw stack-trace verbosity as the default view, burying the one line that mattered.\n\n## Engineering constraint\nStream typed events over SSE or the AG-UI protocol; keep the log queryable after the run.\n\n## Accessibility\n- The log is a `log` role region; announce only warnings, errors and completion, not every event.\n- Raw output toggles have `aria-expanded` and labels naming the step.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "token-streaming",
      "name": "Token streaming",
      "family": "Agent status & progress",
      "component": "StreamingMessage",
      "prompt": "# Build the \"Token streaming\" pattern: `StreamingMessage`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nProgressive text rendering that makes latency legible and lets users bail early.\n\nUse it when: Every LLM text surface — its absence now reads as broken.\n\n## What to build\nDisplays an AI response as it streams in, so people see progress immediately and can stop it early. It keeps the layout stable while text arrives, keeps the partial answer if stopped, and can show timing.\n\n### Props\n- `text`: string, the content received so far. The parent appends chunks; the component never fetches.\n- `status`: \"waiting\" | \"streaming\" | \"stopped\" | \"done\" | \"error\".\n- `format`: \"plain\" | \"markdown\". In markdown mode, buffer incomplete syntax so half-written links, code fences or tables never flash.\n- `onStop()`, `onContinue()` and `onRegenerate()`: callbacks for the controls.\n- `timing?`: { firstTokenMs?: number; totalMs?: number }, shown quietly when present.\n- `reserveLines?`: number, the minimum height to reserve before the first token (default 3).\n\n### States it must render\n- Waiting: reserved space with a quiet \"Thinking\" indicator; Stop is already available.\n- Streaming: text grows with a caret at the end and a prominent Stop. The view follows new text only if the reader is already at the bottom.\n- Stopped: the partial text stays, with a note like \"Stopped at 42 of about 120 words\" and Continue and Regenerate actions.\n- Done: the caret is removed, controls are replaced by actions (copy, regenerate), with optional timing such as \"first words 0.5 s, full answer 6.2 s\".\n- Error: keep what arrived, say the stream broke, and offer Retry from where it stopped.\n\n### Interactions\n- Stop (the button, or Escape while focus is in the conversation) calls `onStop` and shows the stopped state at once, without waiting for the server.\n- Continue resumes from the existing text instead of starting over; Regenerate starts a fresh answer.\n- If the reader scrolls up while streaming, stop auto-scrolling and show a \"Jump to latest\" button.\n\n### Edge cases\n- Chunks that split a word, emoji or markdown token: render only complete characters and tokens.\n- Very fast streams: batch DOM updates per animation frame so the text never stutters.\n- Very long answers: don't re-render the whole message on every chunk.\n- An empty response with status done: show \"No answer returned\" with Retry.\n\n## Must avoid\nStreaming into a layout that reflows on every chunk, so reading is impossible until it stops.\n\n## Engineering constraint\nReserve layout space up front; buffer markdown so partial syntax does not flash.\n\n## Accessibility\n- Announce status changes (started, stopped, done, error) in a polite live region, not every chunk; expose the full text once done.\n- Stop is reachable by keyboard while streaming and keeps focus when pressed.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "long-run-progress",
      "name": "Long-run progress",
      "family": "Agent status & progress",
      "component": "LongRunProgress",
      "prompt": "# Build the \"Long-run progress\" pattern: `LongRunProgress`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nElapsed time, step count and honest expectation-setting for work measured in minutes or hours.\n\nUse it when: Research agents, batch jobs, renders and migrations.\n\n## What to build\nA progress panel for work that takes minutes or hours, showing elapsed time, the current stage out of the total, items processed, the current rate, and an estimated time range based on past runs. When the rate changes it revises the estimate visibly and says why. It renders in a job page, run panel or background tray detail.\n\n### Props\n- `title`: string, e.g. \"Re-scoring health for 1,284 accounts\".\n- `startedAt`: Date and `elapsedSeconds`: number.\n- `stages`: Array<{ id: string; label: string; state: \"done\" | \"running\" | \"pending\"; durationLabel?: string; estimateLabel?: string }>.\n- `items?`: { done: number; total: number; unitLabel: string }, a real count; drives the only determinate bar.\n- `rate?`: { text: string; slowReason?: string }, e.g. \"About 1.1 accounts a second\".\n- `estimate?`: { rangeLabel: string; finishWindowLabel: string; basis: string; revisedAt?: string; history?: number[] }, e.g. \"18 to 25 min, based on the last 9 runs of this size\".\n- `status`: \"running\" | \"slow\" | \"stalled\" | \"done\" | \"failed\".\n- `onNotify?()`: asks to be told when it finishes or stops.\n\n### States it must render\n- Running: elapsed time, \"Stage 2 of 4\", \"Scored 412 / 1,284\", the stage list with durations, the rate, and the estimate range with a likely finish window.\n- Slow: the rate line explains, e.g. \"Down to 0.3 a second: the usage service is rate-limiting us\"; the estimate shows \"Estimate revised at 7:02\" with the new range and \"Nothing is lost, it's just slower\".\n- No estimate: with no history, only elapsed time, stage and count are shown, with \"No past runs to estimate from\".\n- Stalled: no progress for a set period; \"No progress for 3 min\" in amber with the last activity.\n- Done: total time against the usual range, e.g. \"Finished in 22 min, usual 18 to 25\".\n- Failed: the stage it stopped in and what was completed.\n\n### Interactions\n- Notify calls `onNotify` and the button is replaced by \"We'll tell you when it finishes, or if it stops. Safe to close this tab.\"\n- Expanding the estimate shows the history chart of past run durations with this run's elapsed time against them.\n- Stage rows can expand to show their own counts when provided.\n\n### Edge cases\n- No invented percentages: without `items`, show an indeterminate indicator plus stage and elapsed time, never a bar that implies a fraction.\n- Elapsed time beyond the top of the range: widen the range or say \"Taking longer than usual\" rather than holding at the old estimate.\n- Items done greater than total (total grew mid-run): show the new total and note it changed.\n- Very long runs (hours): format elapsed as hours and minutes.\n\n## Must avoid\nA fake determinate progress bar that sticks at 90% and destroys trust in every future estimate.\n\n## Engineering constraint\nPrefer step counts and elapsed time over invented percentages; estimate from historical runs.\n\n## Accessibility\n- Announce stage changes, estimate revisions and completion, not every count tick.\n- The items bar is a `progressbar` with `aria-valuenow` and `aria-valuemax` from real counts; stage list is an ordered list with states in text.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "completion-receipt",
      "name": "Completion receipt",
      "family": "Agent status & progress",
      "component": "RunReceipt",
      "prompt": "# Build the \"Completion receipt\" pattern: `RunReceipt`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA closing summary of what changed, what was skipped, and what still needs a human.\n\nUse it when: Any run with side effects the user did not watch.\n\n## What to build\nA closing summary for a run the person didn't watch: what changed, what was skipped and why, and what still needs a human, built from recorded effects rather than the model's own account. Each change links to its undo where possible, and items needing a person are actionable in place. It renders at the end of a run log, in an inbox or as a notification detail view.\n\n### Props\n- `run`: { title: string; finishedAt: Date; durationLabel: string; startedBy: string; scopeLabel?: string; recordedActionCount: number }, e.g. \"checked 1,906 charges, built from 9 recorded actions\".\n- `changed`: Array<{ id: string; label: string; detail?: string; value?: string; undoUntil?: Date; undone?: boolean }>.\n- `skipped`: Array<{ id: string; label: string; reason: string }>.\n- `needsHuman`: Array<{ id: string; label: string; reason: string; actions: Array<{ id: string; label: string }> }>.\n- `totals?`: Array<{ label: string; value: string }>, e.g. \"5 refunded, $6,210\".\n- `onUndo(changeId: string)` and `onNeedsAction(itemId: string, actionId: string)`: callbacks. The component never performs the actions itself.\n- `activeTab`: \"changed\" | \"skipped\" | \"needs\" | \"log\" and `onTabChange`, controlled.\n- `logSlot?`: React.ReactNode, the full step log.\n\n### States it must render\n- Needs you: a header badge \"1 needs you\" in amber, tab counts (\"5 changed\", \"2 skipped, with reasons\", \"1 needs you\"), and the needs tab opened by default.\n- Changed: each change with its value and an Undo button, plus a note like \"Each refund can be reversed for 24 hours. Customers were not emailed yet.\"\n- Skipped: each skipped item with a specific reason, e.g. \"Already refunded on 12 Aug. Nothing to do.\"\n- Undone: the change is struck through with \"Undone by you\" and totals recalculate.\n- All settled: once needs is empty, the badge turns green \"All settled\".\n- Nothing changed: \"The run finished without changing anything\" with skipped reasons, never a bare \"Done\".\n\n### Interactions\n- Tabs switch lists via `onTabChange`; arrow keys move between tabs.\n- Undo calls `onUndo(id)`; it is hidden after `undoUntil` and replaced by \"Can no longer be undone\".\n- Needs-human actions call `onNeedsAction`; resolved items move into changed or skipped as the parent updates the props.\n\n### Edge cases\n- Long manifests (dozens of files or records): group by type with counts and expand, but never replace the list with a total alone.\n- A recorded effect that failed partway: list it under needs human with what did and didn't happen.\n- Undo windows that expire while open: update the button state without a reload.\n- Counts in totals must equal the list lengths; if they don't, show the list and flag the mismatch.\n\n## Must avoid\nEnding with \"Done!\" and no manifest of the twelve files it edited.\n\n## Engineering constraint\nBuild the receipt from recorded effects, not from asking the model what it thinks it did.\n\n## Accessibility\n- Use a `tablist` with counts in each tab's accessible name; the needs tab is announced first.\n- After Undo, announce what was undone and the new total.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "context-inventory",
      "name": "Context inventory",
      "family": "Memory & context",
      "component": "ContextInventory",
      "prompt": "# Build the \"Context inventory\" pattern: `ContextInventory`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA visible list of what the model can see right now — files, memories, tools, prior turns.\n\nUse it when: Anywhere output quality depends on context the user cannot otherwise inspect.\n\n## What to build\nA panel listing exactly what was assembled into the model's context for a given answer: instructions, tools, files, memories and prior turns, with sizes, what was cut, and how full the window was. It lets a person drop items or ask for fuller inclusion and then re-run. It renders as a popover from a context meter or as a side panel.\n\n### Props\n- `items`: Array<{ id: string; kind: \"system\" | \"tools\" | \"file\" | \"memory\" | \"history\" | \"tool-result\" | \"other\"; title: string; detail?: string; tokens: number; inclusion: \"full\" | \"truncated\" | \"dropped\"; truncation?: { included: string; omitted: string; omittedTokens: number }; removable?: boolean }>, the context as the runtime actually assembled it.\n- `capacity`: { window: number; reservedForReply: number }, the total window and the part kept free for the answer.\n- `forResponse`: { id: string; assembledAt: Date }, which answer this inventory describes.\n- `status`: \"current\" | \"stale\" | \"loading\" | \"unavailable\", controlled by the parent.\n- `pendingChanges?`: Array<{ id: string; deltaTokens: number }>, removals or inclusions queued for the next run.\n- `onRemove(id)`, `onIncludeFull(id)`, `onSummarize?(id)` and `onRerun()`: callbacks. The component never changes the context itself.\n- `formatTokens?`: (n: number) => string, defaults to \"18.4K\".\n\n### States it must render\n- Current: a capacity bar segmented by item, the list with each item's size, and the space left (\"0.3K free\").\n- Truncated item: marked \"Truncated\" with what got in and what was cut in plain words (\"Pages 1–4 of 9 fit; sections 5–9 cut\"), plus Include all and its token cost.\n- No room: Include all is disabled with the shortfall (\"Needs 21.6K, 0.3K free\") and the largest removable items offered as ways to free space.\n- Stale: after changes, a banner says the shown answer used the old context, with Re-run.\n- Loading: skeleton rows. Unavailable: \"Context wasn't reported for this answer\"; never a guessed or intended list.\n\n### Interactions\n- Remove queues the item, previews the new bar and free space, and calls `onRemove`.\n- Include all and Summarize call their callbacks; the bar previews the change before Re-run.\n- Re-run calls `onRerun`; clicking a bar segment moves focus to its row.\n\n### Edge cases\n- An item the person attached but the runtime dropped appears with `inclusion` \"dropped\" and 0 tokens, so \"I attached it\" never reads as \"the model saw it\".\n- Items the person didn't add (tool results, system instructions) are still listed; system items are not removable.\n- Many small items of one kind group into one row (\"14 files, 9.2K\") that expands.\n- Reported totals exceed the window: show the overflow as a warning rather than clipping the bar silently.\n\n## Must avoid\nA context window the user cannot inspect, leaving them to guess why the answer went sideways.\n\n## Engineering constraint\nReport actual assembled context, including truncation, not the intended set.\n\n## Accessibility\n- The capacity bar has a text equivalent (\"111.7K of 112K used\"); segments are not the only way to read sizes.\n- Announce the new free-space figure when a change is queued.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "memory-manager",
      "name": "Memory manager",
      "family": "Memory & context",
      "component": "MemoryManager",
      "prompt": "# Build the \"Memory manager\" pattern: `MemoryManager`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA browsable store of remembered facts and preferences, each editable and deletable.\n\nUse it when: Assistants used daily, where accumulated memory becomes both the value and the risk.\n\n## What to build\nA browsable list of what an assistant remembers, searchable and filterable by category, with each memory's source and where it was last used. A person can correct a single memory in place or delete it on its own. It renders in settings or a side panel.\n\n### Props\n- `memories`: Array<{ id: string; text: string; category: string; source: { label: string; at: Date }; lastUsed?: { label: string; at: Date; responseId?: string }; expiresAt?: Date }>.\n- `categories`: string[], with `category` and `onCategoryChange(c: string)` for controlled filtering (\"All\" included).\n- `query`: string and `onQueryChange(q: string)`, controlled search.\n- `editingId?`: string and `savingId?`: string, which row is being edited or saved.\n- `recentlyDeleted?`: { id: string; text: string }, drives the undo row.\n- `status`: \"loading\" | \"ready\" | \"error\".\n- `onEdit(id: string, text: string)`, `onDelete(id: string)`, `onUndoDelete(id: string)` and `onOpenUsage?(responseId: string)`: callbacks. The component never writes memory itself.\n- `onClearAll?()`: optional and secondary, behind a confirmation; it is never the only way to remove something.\n\n### States it must render\n- Ready: a count header, category chips with counts, and rows showing text, category, source and date, with Edit and Delete on each.\n- Editing: the row becomes an input with Save and Cancel; Save is disabled while the text is empty.\n- Deleted: the row is replaced by \"Deleted. Undo\" for a few seconds before it leaves.\n- Last used: a row can show the answer that used it (\"Used yesterday in renewal prep\"), so a bad answer can be traced to the memory behind it.\n- No matches: \"No memories match\" with a clear-filters action. Empty: explains what kinds of things will appear here.\n- Loading and error: skeleton rows; an error message with Retry.\n\n### Interactions\n- Typing filters live; chips filter by category and combine with the search.\n- Edit swaps the row for an input and focuses it; Enter saves via `onEdit`, Escape cancels.\n- Delete calls `onDelete` immediately and shows Undo; Undo calls `onUndoDelete`.\n- The last-used link calls `onOpenUsage` with the response id.\n\n### Edge cases\n- Editing a memory that was used recently: after saving, note \"Earlier answers used the old value\" next to the usage link.\n- Two memories that contradict each other (e.g. two seat counts): both stay visible and adjacent when searched, so the person picks which to keep.\n- A memory with an expiry shows it (\"expires 5 Oct\") so temporary facts aren't mistaken for permanent ones.\n- Hundreds of memories: group by category and paginate or virtualise; counts stay accurate.\n\n## Must avoid\nMemory as an opaque black box with only a nuclear \"clear all\" option.\n\n## Engineering constraint\nMemory writes are user-visible events; make retrieval attributable so you can debug bad recall.\n\n## Accessibility\n- After a delete, focus moves to the Undo control, then to the next row when it disappears.\n- Announce result counts politely as filters change (\"4 memories\").\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "workspace-scoping",
      "name": "Workspace scoping",
      "family": "Memory & context",
      "component": "ScopeSelector",
      "prompt": "# Build the \"Workspace scoping\" pattern: `ScopeSelector`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nExplicit boundaries — this project, this team, this dataset — for what an agent may recall or search.\n\nUse it when: Multi-client and multi-team environments where leakage between contexts is a real hazard.\n\n## What to build\nA control that shows and sets which sources an agent may search for the current task (this client, the shared handbook, private notes), with presets and a report of what the retrieval layer filtered out. It renders as a popover under the composer or a panel beside a draft.\n\n### Props\n- `sources`: Array<{ id: string; name: string; description: string; kind: \"project\" | \"client\" | \"company\" | \"private\" | \"web\" | \"app\"; included: boolean; locked?: { reason: string } }>.\n- `presets`: Array<{ id: string; label: string; sourceIds: string[] }> with `activePresetId?`: string, e.g. \"This client\" and \"Everything I can access\".\n- `surface`: \"internal\" | \"external\", whether output goes to someone outside the team; drives warnings.\n- `status`: \"applied\" | \"pending\" | \"applying\" | \"error\", controlled by the parent.\n- `filtered?`: { count: number; sourceNames: string[] }, reported by the retrieval layer after a search.\n- `appliesTo`: string, plain words for the boundary, e.g. \"Applies to this reply\".\n- `onChange(sourceIds: string[])` and `onPresetChange(id: string)`: callbacks. The component only requests a scope; enforcement happens in retrieval, never in the prompt.\n\n### States it must render\n- Applied: every source listed with \"Searched\" or \"Blocked\" written beside it.\n- Pending: \"Changes apply to the next search\" with Apply, until the parent confirms.\n- Filtered report: \"9 matches in another client's files and your private notes were filtered out before the agent saw them\" (using real names from `filtered`).\n- Widened on an external surface: choosing a broad preset shows an amber warning listing the private and other-client sources it would add.\n- Applying and error: controls disabled with a busy indicator; on error the previous scope is shown as still in force.\n\n### Interactions\n- Checkbox per source; preset radios set several at once and show \"Custom\" when edited by hand.\n- Apply calls `onChange` with the selected ids; locked sources can't be toggled and show their reason.\n- The filtered report can expand to list the blocked sources by name, not their content.\n\n### Edge cases\n- No source selected: Apply is disabled with \"Select at least one source\".\n- The default is the narrowest preset for the task, never \"Everything I can access\".\n- Private sources on an external surface are locked out unless the parent allows them.\n- A source loses permission mid-session: it flips to Blocked with \"Access removed\".\n\n## Must avoid\nPersonal context bleeding into a client-facing surface because scoping was implicit.\n\n## Engineering constraint\nEnforce scope in the retrieval layer with permission filters, not in the prompt.\n\n## Accessibility\n- Sources are checkboxes in a `fieldset` whose legend states the boundary; Searched and Blocked are text, not colour.\n- Announce the filtered count once after each search.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "recall-attribution",
      "name": "Recall attribution",
      "family": "Memory & context",
      "component": "MemoryAttribution",
      "prompt": "# Build the \"Recall attribution\" pattern: `MemoryAttribution`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nWhen the system uses memory, it says which memory — turning surprise into confirmation.\n\nUse it when: Assistants whose personalisation is otherwise indistinguishable from guessing.\n\n## What to build\nShows which memories an AI answer used: numbered markers in the text tie each personalised claim to a memory, and a list underneath names each memory's source with an excerpt. A person can confirm, mark a memory as not relevant, or forget it from the same place. It renders under or beside an AI message.\n\n### Props\n- `segments`: Array<{ text: string; memoryIds?: string[] }>, the answer split into spans; a span with `memoryIds` drew on those memories.\n- `memories`: Array<{ id: string; title: string; source: { label: string; at: Date; savedBy?: string }; excerpt?: string; forgotten?: boolean }>, exactly the memories retrieved and logged with this response.\n- `responseId`: string, shown with the logged memory ids so the record can be audited.\n- `status`: \"attributed\" | \"none\" | \"unavailable\".\n- `selectedId?`: string and `onSelect(id: string | null)`, controlled selection.\n- `expanded?`: boolean and `onToggle(expanded: boolean)`, whether the memory list is open.\n- `onMarkIrrelevant(memoryId)`, `onForget(memoryId)` and `onCorrect?(memoryId)`: callbacks. The component never edits memory itself.\n\n### States it must render\n- Collapsed: a chip in the message header, e.g. \"Used 3 memories\", and small numbered markers in the text.\n- Expanded: the list of memories with title, source, date and who saved it, each with Not relevant and Forget.\n- Memory selected: its spans are highlighted and its excerpt opens, showing where the fact came from.\n- Marked not relevant: the row dims with \"Won't be used for requests like this\".\n- None: \"No memories used\" stated explicitly, so personalisation is never implied. Unavailable: \"Memory sources weren't logged for this answer\"; nothing is reconstructed after the fact.\n\n### Interactions\n- Clicking a marker or a memory row selects it; clicking again clears the selection.\n- The header chip toggles the list via `onToggle`.\n- Not relevant, Forget and Correct call their callbacks; Forget asks for a one-step confirmation inline.\n\n### Edge cases\n- A memory that shaped the answer without a specific span (tone, sign-off style) is listed under \"Also used\".\n- A memory forgotten after this answer: keep its title, drop the excerpt, and label it \"Forgotten since\".\n- A memory saved by someone else in a shared workspace shows who saved it.\n- Only memories present in `memories` can have markers; a `memoryIds` entry with no matching memory renders no marker and is ignored.\n\n## Must avoid\nPersonalisation that feels like surveillance because the source is never named.\n\n## Engineering constraint\nLog the retrieved memory IDs with the response so the citation is real, not reconstructed.\n\n## Accessibility\n- Markers are buttons labelled like \"Memory 2: prefers email over phone\" with `aria-pressed`.\n- The link between span and memory is shown with an underline or outline as well as colour.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "forget-and-expire",
      "name": "Forget and expire",
      "family": "Memory & context",
      "component": "RetentionControls",
      "prompt": "# Build the \"Forget and expire\" pattern: `RetentionControls`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nControls for temporary context, session-only mode, and automatic expiry of stale memory.\n\nUse it when: Sensitive work, one-off tasks, and regulated data-retention regimes.\n\n## What to build\nA settings panel for what an assistant keeps: a temporary-mode switch, a list of memories with their expiry rules, and a Forget action that shows deletion reaching every store and then proves nothing comes back. It renders in a memory or workspace settings screen.\n\n### Props\n- `temporaryMode`: { on: boolean; description: string } and `onTemporaryChange(on: boolean)`, the session-only switch and what it means in plain words.\n- `memories`: Array<{ id: string; text: string; source: string; expiry: { kind: \"date\" | \"event\" | \"never\"; label: string; at?: Date }; sensitive?: boolean }>.\n- `deletion?`: { memoryId: string; stores: Array<{ id: string; label: string; count: number; unit: string; state: \"pending\" | \"running\" | \"done\" | \"failed\"; error?: string }>; verification: { state: \"pending\" | \"running\" | \"passed\" | \"failed\"; query?: string; resultCount?: number } }, progress reported by the backend.\n- `expired?`: Array<{ id: string; text: string; expiredAt: Date; storesCleared: string[] }>, recent automatic expiries with receipts.\n- `status`: \"idle\" | \"forgetting\" | \"error\", controlled by the parent.\n- `onForget(id)`, `onRetryStore(memoryId, storeId)` and `onExpiryChange?(id, expiry)`: callbacks. The component never deletes anything itself.\n\n### States it must render\n- Idle: each memory with its source and expiry label (\"Auto-forgets in 29 days\", \"Kept until you remove it\"); sensitive ones flagged.\n- Temporary on: the switch shows on with its description, e.g. \"New chats create no memories and are deleted after 24 hours\".\n- Forgetting: the memory is marked as going and a checklist shows each store (list, search index, cache, logs) with counts moving from pending to done.\n- Verified: \"Nothing comes back: a search for the card number returns 0 results\", then the entry leaves the list with a receipt.\n- Partly forgotten: a failed store keeps the entry listed as \"Partly forgotten\" with the failing store named and Retry.\n- Expired receipts and empty: past expiries show which stores were cleared; an empty list says what will appear here.\n\n### Interactions\n- Forget calls `onForget`; other Forget buttons stay usable but queue behind the running one.\n- The temporary switch calls `onTemporaryChange` and updates its description at once.\n- Retry on a failed store calls `onRetryStore`; changing an expiry rule calls `onExpiryChange`.\n\n### Edge cases\n- The entry is removed from the list only when every store reports done and verification passes; removing the row early is never allowed.\n- Verification finds a result: show \"Still found in search index\" as an error, not success.\n- An event-based expiry whose date has passed but hasn't been processed shows \"Due to expire\", not \"Expired\".\n- Turning on temporary mode mid-conversation states whether it applies to the current chat or only new ones, from `description`.\n\n## Must avoid\nA forget action that removes the UI entry while the embedding stays in the index.\n\n## Engineering constraint\nDeletion must propagate to vector stores, caches and logs; prove it in your retention tests.\n\n## Accessibility\n- The store checklist is an ordered list whose step states are written in text, not only icons.\n- Announce the outcome once: \"Forgotten from 4 stores and verified\" or the failure.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "escalation-to-human",
      "name": "Escalation to human",
      "family": "Human handoff",
      "component": "HumanHandoff",
      "prompt": "# Build the \"Escalation to human\" pattern: `HumanHandoff`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA clear transition from agent to person, with the switch of actor made obvious.\n\nUse it when: Support, sales and any flow where the agent's ceiling is reached mid-conversation.\n\n## What to build\nThe chat pieces that move a conversation from an AI agent to a person: an always-visible \"Talk to a person\" control, an inline offer when the agent reaches its limit, and a clear marker showing who is responding now. It renders in a support chat header and message stream.\n\n### Props\n- `status`: \"agent\" | \"offered\" | \"requested\" | \"queued\" | \"connected\" | \"unavailable\" | \"ended\", controlled by the parent.\n- `agent`: { name: string }, always labelled as an AI agent.\n- `human?`: { name: string; role: string; avatarUrl?: string }, the person who joined.\n- `queue?`: { since: Date; estimateMinutes?: number; position?: number }.\n- `offerReason?`: string, why the agent is offering, e.g. \"A billing specialist has to verify a new card\".\n- `carriesOver`: string[], what the person will see, e.g. \"This conversation\", \"The refund already started\".\n- `fallback?`: { label: string; description: string }, e.g. an email callback when nobody is available.\n- `onRequest()`, `onDecline()`, `onCancel()` and `onFallback()`: callbacks. The component never routes anything itself.\n\n### States it must render\n- Agent: the header names the agent with an \"AI agent\" label and shows Talk to a person from the first message.\n- Offered: an inline card with the reason and two equal choices, Bring in a person and Keep chatting.\n- Queued: \"Connecting you to Billing, about 3 min\" with what will carry over, and Cancel.\n- Connected: a divider in the stream (\"A billing specialist joined. You're now talking to a person.\") and the header switches name, avatar and label.\n- Unavailable: no one free right now, with the fallback offered; never a dead end.\n- Ended: a divider saying the person left and who replies next.\n\n### Interactions\n- Talk to a person in the header calls `onRequest` at any point, without needing the agent to fail first.\n- Bring in a person calls `onRequest`; Keep chatting calls `onDecline` and closes the card.\n- Cancel while queued calls `onCancel`; the fallback button calls `onFallback`.\n\n### Edge cases\n- The person asks for a human in free text: the parent sets `requested` and the card confirms rather than arguing.\n- Queue timeout: the parent moves to `unavailable`; the component never quietly returns to the agent.\n- A request while the agent is mid-reply: the request is accepted at once and shown in the stream.\n- The agent rejoins after the person leaves: a second divider makes that switch just as visible.\n\n## Must avoid\nHiding the human option until the user has failed three times and lost the thread entirely.\n\n## Engineering constraint\nRoute by risk and deny by default on timeout; carry the transcript and tool results into the ticket.\n\n## Accessibility\n- Announce each change of who is responding (\"Now talking to a billing specialist, a person\").\n- The header control has a stable accessible name and stays in the same place in tab order.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "shared-review-queue",
      "name": "Shared review queue",
      "family": "Human handoff",
      "component": "ReviewQueue",
      "prompt": "# Build the \"Shared review queue\" pattern: `ReviewQueue`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nAgent output waiting on people, with assignment, priority and age visible to the team.\n\nUse it when: Volume operations where humans review a stream of agent work.\n\n## What to build\nA team inbox of agent output waiting for a human decision, showing owner, priority, age and deadline, with a detail pane for the draft, the agent's own check and the decision. Team figures on decision time and edit rate sit in view so rubber-stamping shows up early. It renders as a full-page inbox or a split view.\n\n### Props\n- `items`: Array<{ id: string; kind: string; title: string; assignee?: { id: string; name: string }; priority: \"high\" | \"normal\" | \"low\"; createdAt: Date; dueAt?: Date; draft: ReactNode; agentCheck: string; confidence?: number }>.\n- `currentUserId`: string.\n- `filter`: \"all\" | \"mine\" | \"unassigned\" and `onFilterChange(f)`.\n- `selectedId?`: string and `onSelect(id: string)`.\n- `stats?`: { decidedToday: number; medianDecisionMs: number; editRate: number; fastApprovals?: number }, computed by the backend.\n- `status`: \"loading\" | \"ready\" | \"error\".\n- `onApprove(id, { viewedMs })`, `onEdit(id)`, `onSendBack(id, reason)` and `onAssign(id, userId)`: callbacks; `viewedMs` is how long the detail was open. The component never applies a decision itself.\n\n### States it must render\n- List: rows with kind, title, assignee or \"Unassigned\", priority and age; a header like \"6 waiting, oldest 3h 10m\"; filter tabs with counts.\n- Over deadline: the age shows \"over SLA\" in amber text.\n- Detail: the draft, the agent's check (\"Matched against the payments log\"), the assignee line, and Approve, Edit, Send back.\n- Someone else's item: decision buttons disabled with \"Assigned to\" and the teammate's name, and an Assign to me action.\n- Team figures: \"31 decided today, median 1m 40s, 18% edited\"; a sudden run of very fast approvals is flagged in amber.\n- Empty view and loading: \"Nothing left in this view\" with other tabs suggested; skeleton rows while loading.\n\n### Interactions\n- There is no select-all or bulk approve; Approve exists only in the detail pane, after the draft is shown.\n- Approving an unassigned item claims it for the current user in the same action.\n- Send back requires a short reason before calling `onSendBack`.\n- Up and Down arrows (or J and K) move through the list; Enter opens the detail.\n\n### Edge cases\n- An item is claimed by someone else while open: switch to the read-only state without losing the viewer's place.\n- Ages refresh each minute without reordering the list under the pointer.\n- Edit rate at 0% over a full day, or median decision time under a few seconds, is shown as a warning, not a success.\n- A decided item leaves the list with a short Undo, and focus moves to the next item.\n\n## Must avoid\nQueues that only grow, teaching reviewers to batch-approve without reading.\n\n## Engineering constraint\nTrack per-item decision time and correction rate; they are your early rubber-stamp detector.\n\n## Accessibility\n- The list is a `listbox` (or a list of links) with each option labelled by kind, title, priority and age.\n- Announce decisions and assignment changes politely; don't announce age updates.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "context-packet-transfer",
      "name": "Context packet transfer",
      "family": "Human handoff",
      "component": "HandoffBrief",
      "prompt": "# Build the \"Context packet transfer\" pattern: `HandoffBrief`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA compact briefing — what was tried, what is known, what the ask is — handed to the receiving human.\n\nUse it when: Escalations and shift handovers where the human's first minute is otherwise spent reconstructing.\n\n## What to build\nA compact briefing for the person receiving escalated work: the ask, what is known (with where each fact came from), what was tried, what is still unknown, and what the customer has already answered. The raw transcript is kept as a secondary tab for audit. It renders in a ticket side panel or a handoff inbox.\n\n### Props\n- `ask`: string, the one thing the receiver needs to do, e.g. \"Send the $1,240 refund to the new card ending 0932\".\n- `known`: Array<{ fact: string; source: string }>, facts the run verified, each with its record (log, account, policy).\n- `tried`: Array<{ action: string; outcome: string }>.\n- `unknown`: string[], open questions the receiver must resolve.\n- `answered?`: Array<{ label: string; value: string }>, details the customer won't be asked again.\n- `transcript`: Array<{ who: string; at: Date; text: string }>, the full record, shown only on its own tab.\n- `meta`: { from: string; to?: string; waitingSince: Date; note?: string }, plus `wordLimit?`: number (default 120).\n- `status`: \"waiting\" | \"claimed\" | \"loading\" | \"error\" and `claimedBy?`: { name: string; at: Date }.\n- `onClaim()`, `onFlagInaccurate?(section: \"ask\" | \"known\" | \"tried\" | \"unknown\")` and `onTabChange?(tab)`: callbacks.\n\n### States it must render\n- Waiting: the Brief tab is open by default with the ask first, then Known, Tried, Still unknown, a word count and a waiting timer; Take this ticket is the primary action.\n- Claimed: a line \"You took this at 09:07\" replaces the button.\n- Already answered: a checklist of confirmed details, with anything still to ask marked with a question mark.\n- Transcript tab: the full messages with a note that the brief is built from run state, not summarised from this.\n- Loading and error: skeleton sections; on error the transcript tab still works.\n\n### Interactions\n- Take this ticket calls `onClaim` and moves focus to the ask.\n- Tabs switch between Brief and Transcript (with the message count on the tab).\n- Each section can be flagged as inaccurate via `onFlagInaccurate`, so bad packets get fixed at the source.\n\n### Edge cases\n- Over `wordLimit`: show the count in amber and collapse list items beyond three per section behind \"+2 more\"; never pad to fill.\n- Empty sections say so (\"Nothing tried yet\") rather than disappearing.\n- Missing `ask`: render \"No ask provided\" as a visible problem instead of falling back to the transcript.\n- A fact without a `source` gets an \"unverified\" marker.\n\n## Must avoid\nDumping the raw transcript and calling it context, so the reviewer does the summarising.\n\n## Engineering constraint\nGenerate the packet from structured run state, and cap length; noisy packets get skimmed.\n\n## Accessibility\n- The ask is the brief's heading so it is read first; tabs follow the tablist pattern.\n- The waiting timer is not announced each tick; announce only when the status changes.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "mid-stream-takeover",
      "name": "Mid-stream takeover",
      "family": "Human handoff",
      "component": "TakeoverControl",
      "prompt": "# Build the \"Mid-stream takeover\" pattern: `TakeoverControl`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nA human takes the controls without cancelling the run, then hands back.\n\nUse it when: Live sessions and pair-work agents where intervention windows are seconds long.\n\n## What to build\nA control bar and field-level authorship markers for a surface an agent is operating, such as a form, browser or document. It shows who is driving, lets a person take control instantly without cancelling the run, keeps everything the agent already did, and hands back with the person's edits. It renders above the shared workspace.\n\n### Props\n- `driver`: \"agent\" | \"human\" | \"handing-back\" | \"resumed\", controlled by the parent.\n- `activity?`: string, what the agent is doing now, e.g. \"Filling Reason\".\n- `fields`: Array<{ id: string; label: string; author: \"agent\" | \"human\" | \"empty\"; partial?: boolean }>, who last wrote each field.\n- `pausedAt?`: { fieldId?: string; at: Date }, where the agent stopped when control was taken.\n- `changes`: Array<{ fieldId: string; from: string; to: string }>, edits the person made since taking control.\n- `finalStepNote`: string, who commits, e.g. \"The agent won't submit on its own\".\n- `log?`: Array<{ who: \"agent\" | \"human\"; text: string; at: Date }>, a session timeline.\n- `onTakeControl()`, `onHandBack(changes)` and `onResetField?(fieldId)`: callbacks. The component never pauses or resumes the agent itself.\n\n### States it must render\n- Agent driving: an accent bar \"The agent is filling this form\" with the current activity and Take control.\n- In control: \"You're in control. The agent paused at Reason. The 3 fields it filled stay as they are.\"; fields show who filled them.\n- Editing: fields the person changes are marked \"edited by you\" with the earlier value available.\n- Handing back: a summary of the person's changes and \"The agent will continue from your edits\".\n- Resumed: the agent continues; human-edited fields stay marked and the timeline records the handover both ways.\n- Nothing filled yet: taking control says so instead of implying work was kept.\n\n### Interactions\n- Take control is one click (and a documented shortcut); the bar switches to In control at once while `onTakeControl` fires.\n- Hand back calls `onHandBack` with `changes`; with no changes it says the agent will continue where it paused.\n- Reset on a human-edited field calls `onResetField` to restore the agent's value.\n\n### Edge cases\n- Takeover mid-typing: the half-typed value stays, marked `partial`, never cleared.\n- After hand-back, fields with `author` \"human\" show \"kept: edited by you\"; the component flags any later agent write to them as a conflict rather than hiding it.\n- The person takes control again during resume: allowed immediately, with no second confirmation.\n- The final commit stays with the person when `finalStepNote` says so; the submit control is never triggered by the agent's state.\n\n## Must avoid\nTakeover that discards everything the agent had built, punishing the user for intervening.\n\n## Engineering constraint\nCheckpoint state before yielding control; the agent must resume from the human's edits, not its own.\n\n## Accessibility\n- Announce driver changes in a polite live region; on takeover move focus to the field where the agent paused.\n- Authorship markers are text (\"edited by you\"), not only colour.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "return-to-agent",
      "name": "Return to agent",
      "family": "Human handoff",
      "component": "ResumeFromReview",
      "prompt": "# Build the \"Return to agent\" pattern: `ResumeFromReview`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nAfter human work, the agent resumes with the correction folded in and says what it now knows.\n\nUse it when: Approval-gated pipelines and any loop where the human is a step, not the terminus.\n\n## What to build\nThe review step of a paused agent run: it shows the checkpoint, which steps are already done and will be kept, the output to correct, and an optional note. After the person sends it back, it shows the agent's acknowledgement of what changed and which step it resumes from. It renders in a run view or an approvals inbox.\n\n### Props\n- `steps`: Array<{ id: string; title: string; detail?: string; state: \"done\" | \"waiting\" | \"pending\" | \"running\" | \"will-rerun\" }>.\n- `checkpoint`: { afterStepId: string; savedAt: Date } and `resumeFromStepId`: string.\n- `fields`: Array<{ id: string; label: string; value: string; original: string; validate?: (v: string) => string | null }>, the output the reviewer may correct.\n- `note`: string and `onNoteChange(note: string)`, an optional instruction for the agent.\n- `decisionId`: string, sent with every submission so resuming is idempotent.\n- `status`: \"awaiting-review\" | \"submitting\" | \"resumed\" | \"already-decided\" | \"error\", controlled by the parent.\n- `acknowledgement?`: { kept: string[]; changed: Array<{ label: string; from: string; to: string }>; learned?: string }, the agent's reply after resuming.\n- `decidedBy?`: { name: string; at: Date }.\n- `onSendBack({ decisionId, fields, note })`, `onApprove(decisionId)` and `onFieldChange(id, value)`: callbacks. The component never restarts the run.\n\n### States it must render\n- Awaiting review: the step list with done steps ticked, the paused step highlighted, \"Checkpoint saved after step 3. Nothing sent.\", and the editable fields.\n- Edited: changed fields show the original struck through beside the new value; Send back shows \"Resumes at step 5\".\n- Submitting: actions disabled with a busy indicator.\n- Resumed: the acknowledgement, e.g. \"Kept from before: seat usage and 14 tickets, not re-run. Changed: seats 40 → 45. Continuing at step 5.\"\n- Already decided: \"Sent back by\" with the reviewer's name and time with no actions, when the same decision arrived from elsewhere.\n- Error: \"Couldn't resume. Your edits are kept.\" with Retry using the same `decisionId`.\n\n### Interactions\n- Editing a field calls `onFieldChange`; Send back calls `onSendBack` only when every `validate` returns null.\n- Approve as is calls `onApprove`.\n- A second click while submitting does nothing.\n\n### Edge cases\n- An edit that invalidates an earlier step: that step is shown as `will-rerun` with the reason, so the person sees exactly what repeats.\n- Checkpoint too old to resume: say so and offer the parent's restart path as a separate, clearly labelled action.\n- Retrying after a network error reuses `decisionId`, so the run can't resume twice.\n- No changes and no note: Send back is replaced by Approve as is.\n\n## Must avoid\nRequiring a full restart after approval, wasting the run and the reviewer's decision.\n\n## Engineering constraint\nPersist run state keyed to the approval so resume is idempotent and replay-safe.\n\n## Accessibility\n- The step list is an ordered list with each state in text; the paused step has `aria-current=\"step\"`.\n- After resuming, move focus to the acknowledgement.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "agent-selected-components",
      "name": "Agent-selected components",
      "family": "Generative UI",
      "component": "RegisteredComponentRenderer",
      "prompt": "# Build the \"Agent-selected components\" pattern: `RegisteredComponentRenderer`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nThe agent chooses from a typed library of components and populates them; it cannot invent primitives.\n\nUse it when: The default safe shape of generative UI in a product with an existing design system.\n\n## What to build\nRenders an agent's UI request by looking up the requested component name in a typed registry from the design system and validating the props against that component's schema. Anything unregistered or invalid falls back to plain text, never to agent-written markup. It renders inside a chat message or any slot that shows agent output.\n\n### Props\n- `call`: { id: string; name: string; args: unknown }, the agent's render or tool call.\n- `registry`: Record<string, { component: ComponentType<P>; validate: (args: unknown) => { ok: true; props: P } | { ok: false; errors: string[] }; label: string; skeleton?: ReactNode }>, the only components the agent may use.\n- `status`: \"streaming\" | \"ready\", whether `args` is still arriving.\n- `showCall?`: boolean, reveals the raw call (name and arguments) for inspection.\n- `fallbackText?`: string, the agent's plain-text answer to show when the call can't render.\n- `onRejected?(call, reason: \"unknown\" | \"invalid\", errors?: string[])`: reports refusals for monitoring.\n- `onComponentAction?(callId, action)`: passes events from the rendered component (e.g. a button inside a card) back to the agent.\n\n### States it must render\n- Streaming: the registered component's skeleton, sized like the real thing; unknown names show a neutral placeholder.\n- Rendered: the design-system component with validated props.\n- Invalid props: the text fallback with a quiet note (\"Couldn't show this as an invoice table\"); errors go to `onRejected`.\n- Unknown component: \"PieChart isn't available here\" plus the text fallback; nothing is improvised.\n- Call shown: a disclosure under the output with the component name and formatted arguments.\n\n### Interactions\n- The show-call toggle opens and closes the inspection panel.\n- Actions inside the rendered component are forwarded with `onComponentAction`, tagged with the call id.\n- Rejections fire `onRejected` once per call, not on every streamed chunk.\n\n### Edge cases\n- Arguments containing HTML or script are rendered as text; `dangerouslySetInnerHTML` is never used.\n- Unknown extra props are stripped by validation, not passed through.\n- A name that differs only in case or spelling from a registered one is still rejected, not guessed.\n- Arguments still streaming: validate only once `status` is ready; don't flash errors on partial data.\n\n## Must avoid\nLetting the model emit arbitrary markup, producing off-system UI nobody can maintain.\n\n## Engineering constraint\nAG-UI is supported across LangGraph, CrewAI, Google ADK, AWS Strands and more — render by tool name.\n\n## Accessibility\n- The rendered component keeps its own semantics; the fallback is announced like any other message.\n- The show-call control is a disclosure button with `aria-expanded`.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "inline-generated-form",
      "name": "Inline generated form",
      "family": "Generative UI",
      "component": "ToolInputForm",
      "prompt": "# Build the \"Inline generated form\" pattern: `ToolInputForm`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nWhen the agent needs input, it renders the specific fields it needs instead of asking in prose.\n\nUse it when: Mid-task gaps: missing parameters, ambiguous choices, required confirmations.\n\n## What to build\nA compact form the agent places in the conversation when it needs specific inputs to finish a tool call. Fields are generated from the tool's schema, validated before submission, and returned as typed values rather than prose. It renders inline in a chat message.\n\n### Props\n- `title`: string, `intro?`: string, and `toolName`: string (shown quietly, e.g. \"refunds.create\").\n- `schema`: a JSON Schema object subset (string, number with min/max, enum, boolean, required) describing the tool's missing parameters.\n- `uiHints?`: Record<string, { label: string; help?: string; widget?: \"text\" | \"money\" | \"radio\" | \"select\" | \"checkbox\"; optionDetails?: Record<string, string> }>, labels and helper text such as \"Up to $1,240.00\".\n- `suggested?`: Record<string, unknown>, values the agent prefilled, marked as suggestions.\n- `status`: \"pending\" | \"submitting\" | \"submitted\" | \"cancelled\" | \"superseded\" | \"error\", controlled by the parent.\n- `serverErrors?`: Record<string, string>, field errors returned by the tool.\n- `submittedValues?`: Record<string, unknown>, shown in the receipt.\n- `onSubmit(values)`, typed from the schema, and `onCancel()`: callbacks. The component never calls the tool itself.\n\n### States it must render\n- Pending: the agent's one-line intro, then labelled fields with helper text, and Submit and Cancel.\n- Invalid: inline errors under each failing field, from schema rules (\"Must be $1,240.00 or less\").\n- Submitting: fields read-only and Submit busy.\n- Submitted: collapses to a receipt listing the values sent.\n- Cancelled or superseded: a one-line note (\"Cancelled\", \"Answered in chat instead\") with the fields hidden.\n- Error: server field errors shown on their fields and the form editable again.\n\n### Interactions\n- Submit validates against `schema`; if anything fails, focus moves to the first invalid field and nothing is sent.\n- Enter in a text field submits; radio groups use arrow keys.\n- Cancel calls `onCancel` so the agent knows the person declined rather than waiting for prose.\n\n### Edge cases\n- A schema type with no widget falls back to a text input with a format hint; required fields are never dropped.\n- Suggested values are visibly marked and count as unconfirmed until the person submits.\n- An enum with an \"other\" option reveals a free-text field only when chosen.\n- More than about six fields: group them into short steps with a progress label rather than one long form.\n\n## Must avoid\nAsking three questions in a paragraph and hoping the user answers all of them in order.\n\n## Engineering constraint\nValidate against the tool schema client-side; return structured values, not re-parsed prose.\n\n## Accessibility\n- The form is labelled by its title; radio groups are `fieldset`s with legends; errors are tied with `aria-describedby`.\n- When the form appears mid-conversation, announce it (\"The agent needs 3 details\") without stealing focus.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "composed-dashboard",
      "name": "Composed dashboard",
      "family": "Generative UI",
      "component": "ComposedDashboard",
      "prompt": "# Build the \"Composed dashboard\" pattern: `ComposedDashboard`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nThe agent assembles a layout of charts and panels answering the question just asked.\n\nUse it when: Analytics and monitoring where the useful view differs with every question.\n\n## What to build\nA grid of panels the agent assembled to answer one question, built from a layout spec that can be saved, re-run and shared. Every panel shows the query behind it, which a person can read and edit. It renders in an analytics workspace below the question.\n\n### Props\n- `question`: string, shown at the top.\n- `layout`: { id?: string; generatedAt: Date; sources: string[]; panels: Array<{ id: string; type: \"kpi\" | \"bar\" | \"line\" | \"table\" | \"text\"; title: string; query: { text: string; language: string }; state: \"loading\" | \"ready\" | \"empty\" | \"error\" | \"stale\"; error?: string }> }, the persistable spec.\n- `renderPanel(panel)`: (panel) => ReactNode, draws the chart or table; the dashboard owns layout and chrome only.\n- `saved?`: { id: string; name: string }, set once the layout is saved.\n- `savedViews?`: Array<{ id: string; name: string }>, plus `onOpenSaved(id)`.\n- `onSave(name: string)`, `onEditQuery(panelId, query)`, `onRemovePanel(panelId)` and `onRerun()`: callbacks. The component never queries data itself.\n\n### States it must render\n- Assembling: one skeleton per panel in the spec, in final positions.\n- Ready: panels with values, and a provenance line such as \"Built 4 panels from billing and onboarding data, 1 Oct 09:12\" in the AI accent.\n- Query open: a panel's query is visible in a code block with Edit.\n- Stale panel: after a query edit, the panel shows \"Query changed, run to update\" until re-run.\n- Panel error or empty: that panel says why (\"No rows\", \"No access to onboarding\") with its query; other panels are unaffected.\n- Saved: \"Saved as Q3 churn drivers\" and the view appears in the saved list, re-runnable.\n\n### Interactions\n- Each panel has a Query disclosure; Edit turns it into a text area, and Run calls `onEditQuery`.\n- Save view asks for a name (prefilled from the question) and calls `onSave`.\n- Remove panel calls `onRemovePanel`; Re-run calls `onRerun` and keeps the layout unchanged.\n\n### Edge cases\n- Re-running a saved view refreshes data and timestamp but never re-generates the layout.\n- A panel whose query references data the person can't access shows a permission message, not an empty chart.\n- Under 640 px panels stack in spec order, KPIs first.\n- A layout with no panels: show the question with \"No panels were produced\" and Re-run.\n\n## Must avoid\nGenerating a fresh throwaway layout every time, with no way to pin, save, or share the good one.\n\n## Engineering constraint\nEmit a layout spec you can persist and re-run; keep the underlying query visible and editable.\n\n## Accessibility\n- Each panel is a `region` labelled by its title; every chart has a short text summary or table alternative from `renderPanel`.\n- Announce when all panels finish loading, not each one.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "artifact-canvas",
      "name": "Artifact canvas",
      "family": "Generative UI",
      "component": "ArtifactCanvas",
      "prompt": "# Build the \"Artifact canvas\" pattern: `ArtifactCanvas`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nSubstantial output moves out of the transcript into a durable side surface that can be edited and versioned.\n\nUse it when: Documents, code, tables and designs — anything the conversation is about rather than made of.\n\n## What to build\nA side surface beside the conversation that holds one substantial artifact (a document, code file or table) with a stable identity, a version list, diffs, editing and export. The chat refers to versions instead of repeating the whole thing. It renders as a resizable pane next to chat, or a full-screen sheet on narrow screens.\n\n### Props\n- `artifact`: { id: string; title: string; kind: \"document\" | \"code\" | \"table\" }.\n- `versions`: Array<{ id: string; number: number; createdAt: Date; author: \"agent\" | \"human\"; summary: string }>, e.g. \"v2 · Wrote the Pricing section\".\n- `latestVersionId`: string, plus `viewingVersionId`: string and `onViewVersion(id: string)`.\n- `content`: ReactNode, the version being viewed, and `diff?`: Array<{ type: \"added\" | \"removed\" | \"same\"; text: string }> against the previous version.\n- `mode`: \"view\" | \"edit\" | \"diff\" and `onModeChange(mode)`.\n- `status`: \"idle\" | \"agent-writing\" | \"saving\" | \"error\", with `writingSection?`: string naming where the agent is working.\n- `onSaveEdit(content: string)`, `onRestore(versionId: string)`, `onExport(format: string)` and `onClose()`: callbacks. The component never persists versions itself.\n\n### States it must render\n- Latest: title, version number and content, with the version picker and Export in the header.\n- Agent writing: the section being changed is marked with the AI accent and \"Writing Pricing\"; the rest stays readable.\n- Older version: a banner \"Viewing v1\" with Back to latest and Restore.\n- Diff: additions and removals since the previous version, marked with more than colour.\n- Edited by a person: saving creates a new version authored by \"You\".\n- Saving and error: a busy indicator; on error, unsaved edits stay in the editor with Retry.\n\n### Interactions\n- Picking a version calls `onViewVersion`; Back to latest returns to `latestVersionId`.\n- Restore calls `onRestore`, which creates a new version from the old one; nothing is deleted.\n- Edit switches to an editor; Save calls `onSaveEdit`; Escape cancels after confirming if there are changes.\n\n### Edge cases\n- The person edits while the agent is writing: the agent's change waits and a banner offers to review it, so neither overwrites the other.\n- Many versions: the picker lists newest first with summaries and stays scrollable.\n- Under 640 px the canvas opens as a sheet with a Back to chat control that keeps the viewed version.\n- A diff too large to show inline collapses unchanged runs (\"12 unchanged lines\").\n\n## Must avoid\nLong deliverables trapped in chat scroll, where every revision creates another buried copy.\n\n## Engineering constraint\nVersion artifacts server-side with stable IDs; diff between versions rather than re-emitting whole.\n\n## Accessibility\n- The canvas is a `region` labelled with the title and version; announce new versions once (\"v3 ready\").\n- Opening the canvas moves focus to its heading; closing returns focus to the reference that opened it.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    },
    {
      "slug": "progressive-ui-streaming",
      "name": "Progressive UI streaming",
      "family": "Generative UI",
      "component": "StreamingLayout",
      "prompt": "# Build the \"Progressive UI streaming\" pattern: `StreamingLayout`\n\n## Stack (edit this block to match your project)\n- React 18+ with TypeScript: one function component, typed props, no `any`.\n- Tailwind CSS, using your design system's tokens if you have them.\n- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.\n- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.\n\n## What the pattern is for\nSkeletons and partial components render as the agent's structured output arrives.\n\nUse it when: Any generative UI slower than a few hundred milliseconds.\n\n## What to build\nRenders generative UI from a structured object while it streams, reserving space for every expected part up front and filling values in place in whatever order they arrive. People can read and click finished parts while the rest loads. It renders wherever generated panels, cards or reports appear.\n\n### Props\n- `shape`: Array<{ id: string; kind: \"kpi\" | \"chart\" | \"table\" | \"text\"; count?: number; minHeight: number }>, the expected layout taken from the output schema, not from content.\n- `partial`: DeepPartial<T>, the object received so far; the parent parses the stream.\n- `isComplete(slotId, value)`: (id: string, value: unknown) => boolean, whether a slot's value is valid enough to show.\n- `renderSlot(slotId, value)`: (id: string, value: unknown) => ReactNode, draws a finished slot.\n- `status`: \"waiting\" | \"streaming\" | \"complete\" | \"stopped\" | \"error\", controlled by the parent.\n- `onStop()` and `onRetry(slotIds?: string[])`: callbacks.\n- `metrics?`: { firstContentMs?: number; layoutShifts?: number; completeMs?: number }, shown quietly when present.\n\n### States it must render\n- Waiting: the full skeleton for every slot in `shape` at final size, with a Stop control.\n- Streaming: finished slots show real content and are interactive; others keep their skeleton; nothing moves.\n- Complete: skeletons gone, optional timing such as \"first content 0.4 s, complete 3.1 s, 0 layout shifts\".\n- Stopped: finished slots stay; unfinished ones say \"Not generated\" with Retry for just those.\n- Error: as stopped, plus a short message that the stream broke.\n\n### Interactions\n- A slot swaps from skeleton to content only when `isComplete` returns true, in place.\n- Stop calls `onStop` and shows the stopped state at once.\n- Retry calls `onRetry` with the unfinished slot ids; focus is never moved by arriving content.\n\n### Edge cases\n- Values arriving out of order (the last table row before the first KPI): each fills its own slot; order of arrival never changes position.\n- Half-arrived values (\"3.\" of \"3.4%\", a chart missing its data array) stay as skeletons until complete.\n- More items than `count`: append below the reserved block; fewer: collapse only the trailing empty slots once complete.\n- Very fast streams: batch updates per animation frame.\n\n## Must avoid\nLayout that jumps on every chunk, so the user cannot start reading or clicking.\n\n## Engineering constraint\nStream partial objects against a schema and reserve space by expected shape, not measured content.\n\n## Accessibility\n- Set `aria-busy` on the container until complete; announce \"Ready\" once rather than each slot.\n- Skeletons are hidden from screen readers; each finished slot gets its own label.\n\n## House rules\n- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live=\"polite\"`; respect `prefers-reduced-motion`.\n- All user-facing text comes from props (or a `labels` prop) so it can be translated.\n- 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.\n- Fits a 360 px wide container without horizontal scrolling.\n\n## Acceptance checks\n- [ ] Every state above can be rendered by passing props, with no internal fake data.\n- [ ] Every interaction and edge case above behaves as described.\n- [ ] Nothing in \"Must avoid\" can happen with this component.\n\n## Deliver\n1. The component file with its exported prop types.\n2. A short usage example that renders each state.\n"
    }
  ]
}