# Build the "Ambient triage" pattern: `TriagedQueue`

## Stack (edit this block to match your project)
- React 18+ with TypeScript: one function component, typed props, no `any`.
- Tailwind CSS, using your design system's tokens if you have them.
- Presentational only: data comes in through props, decisions go out through callbacks. No fetching or global state inside.
- If your codebase already has conventions (naming, shared components, tokens), follow them over this spec.

## What the pattern is for
Incoming work sorted, routed or drafted before the user arrives, with the reasoning available on tap.

Use it when: High-volume queues: support inboxes, alerts, leads, code review.

## What to build
A 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.

### Props
- `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.
- `groups`: Array<{ id: string; label: string; tone: "needs-person" | "ai" | "neutral"; destination?: string }>, the buckets in display order, e.g. "Routed to Billing".
- `order`: "triaged" | "arrival", controlled; arrival order lists every item by `arrivalIndex`, ignoring groups.
- `selectedId?`: string, the item whose reasoning is open.
- `triagedAt?`: Date and `status`: "ready" | "triaging" | "error", for the header line, e.g. "38 new overnight, sorted at 07:02".
- `onOrderChange(order)`, `onSelect(id | null)`: callbacks.
- `onMove(id: string, toGroupId: string)`: the person overrides the placement. The component never re-routes items itself.
- `onRuleFeedback?(ruleId: string, verdict: "keep" | "stop")`: lets the person correct the rule, not just the one item.

### States it must render
- Triaged: items grouped under labelled headers with counts; each row shows a short rule chip such as "Billing keyword + invoice attached".
- Reasoning open: the selected item shows the full rule, the signals it matched, confidence, and a "Move to" control listing the other groups.
- 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.
- Moved: an item the person re-placed shows "Moved by you" instead of the rule chip, and the old group count drops.
- 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.
- Empty group: the header stays with "Nothing here" so the person knows the bucket exists.

### Interactions
- Clicking a rule chip or row calls `onSelect` and opens the reasoning beside (or below at narrow widths) the list.
- "Move to" calls `onMove`; afterwards offer "Stop applying this rule" and "Keep the rule", which call `onRuleFeedback`.
- The order toggle is a two-option segmented control calling `onOrderChange`; selection and scroll position are kept across the switch.
- Groups routed away from the person (to another team) can be collapsed but always show their count.

### Edge cases
- An item with no rule (arrived after triage ran): place it in a "Not sorted yet" group, never in a guessed one.
- Low confidence placements (below a threshold you pass in): mark the chip "Unsure" and sort them first in their group.
- Hundreds of items in one group: show the first 20 with "Show all n", keeping the count accurate.
- The same rule routed many items: the reasoning view says how many items that rule moved this run.

## Must avoid
Silent routing with no override, so the user cannot learn or correct the rule that hid their work.

## Engineering constraint
Keep the pre-triage ordering recoverable and expose the routing rule that fired.

## Accessibility
- 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>".
- After a move, announce "Moved to <group>" and keep focus on the same item in its new place.

## House rules
- Real `<button>`/`<input>` elements, full keyboard use, visible focus; announce status changes with `aria-live="polite"`; respect `prefers-reduced-motion`.
- All user-facing text comes from props (or a `labels` prop) so it can be translated.
- One consistent accent marks anything the AI made or is doing; amber means "needs a person", red means risky, green means done. Never rely on colour alone.
- Fits a 360 px wide container without horizontal scrolling.

## Acceptance checks
- [ ] Every state above can be rendered by passing props, with no internal fake data.
- [ ] Every interaction and edge case above behaves as described.
- [ ] Nothing in "Must avoid" can happen with this component.

## Deliver
1. The component file with its exported prop types.
2. A short usage example that renders each state.
