# Build the "Reference token layering" pattern: `ReferenceTokenComposer`

## 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
Typed @-mentions that pin exact objects — files, people, tickets, tables — into the prompt as structured references.

Use it when: Products with a real object graph, where precision beats prose.

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

### Props
- `value`: Array<string | { token: { id: string; type: string; label: string; meta?: string } }> and `onChange(value)`, the composer content as text and resolved tokens.
- `categories`: Array<{ type: string; label: string }>, the object types offered in the picker.
- `results`: Array<{ id: string; type: string; label: string; meta?: string }> and `onSearch(query: string, type?: string)`, picker results supplied by the parent.
- `resolution?`: Array<{ id: string; status: "resolved" | "missing" | "noAccess" }>, the parent's check of each token at send time.
- `status`: "editing" | "picking" | "sending" | "error".
- `onSend(payload: { text: string; refs: Array<{ id: string; type: string }> })`: sends text plus stable IDs. The component never sends names to be searched again.

### States it must render
- Editing: text with inline tokens showing type icon and label, each removable.
- Picking: a popover below the caret with category tabs and results, each showing type and disambiguating meta (path, owner, date).
- Resolved set: a line under the composer such as "Will read exactly 2 records", listing each token.
- 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.
- Sending: tokens locked; error state keeps the content.

### Interactions
- Typing @ opens the picker and calls `onSearch` as the query changes; Tab or arrow keys switch category.
- Enter or click inserts the highlighted result as a token; Escape closes the picker and leaves the typed @ text as plain text.
- Backspace next to a token selects it first, then removes it on a second press.
- Send calls `onSend` with token IDs, not their labels.

### Edge cases
- Two records with the same label: the picker must show meta to tell them apart, and the token keeps the chosen ID.
- Text that looks like a name but was not picked stays plain text and is never treated as a reference.
- A token whose record was renamed: keep the ID, show the new label from `resolution` if provided.
- Pasted text containing tokens from elsewhere: paste as plain text unless the IDs can be resolved.

## Must avoid
Tokens that look precise but resolve to a fuzzy search behind the scenes, so results silently miss the target.

## Engineering constraint
Resolve tokens to stable IDs at send time and render the resolved set back to the user.

## Accessibility
- The picker follows the combobox pattern (`aria-expanded`, `aria-activedescendant`); each token's accessible name includes its type, e.g. "Account: Acme renewal".
- Announce the resolved count before send when it changes.

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