# Build the "Ghost completion" pattern: `GhostCompletionInput`

## 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
Greyed inline prediction the user accepts with a key, keeping the model subordinate to the typist.

Use it when: High-frequency typing surfaces where interruption cost is higher than error cost.

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

### Props
- `value`: string and `onChange(value: string)`, the controlled text the person has typed.
- `suggestion?`: string, the predicted continuation for the current cursor position, supplied by the parent after its debounce.
- `suggestionFor?`: string, the exact `value` the suggestion was computed for; a suggestion for any other value is ignored.
- `acceptKey?`: "Tab" | "ArrowRight" (default "Tab") and `acceptWordKey?`: string, accept the whole suggestion or just the next word.
- `onAccept(text: string, mode: "all" | "word")`, `onDismiss()` and `onRequest(value: string, cursor: number)`: the parent debounces, cancels in-flight requests and fetches.
- `latencyMs?`: number, optionally shown as a quiet "Suggested in 138 ms" hint.
- `enabled`: boolean, lets a person turn suggestions off.

### States it must render
- Typing: plain text, no ghost; nothing appears while keys are being pressed.
- Suggesting: dimmed text after the cursor and a small key hint ("Tab to accept, Esc to dismiss").
- Partially accepted: the accepted words become normal text and the rest stays ghosted.
- Dismissed: ghost removed until the next pause.
- Off: `enabled` false, behaves as a normal input.

### Interactions
- 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.
- Any character key that does not match the next ghost character clears the ghost and is typed normally; a matching character consumes it.
- Escape, moving the cursor, clicking elsewhere or selecting text clears the ghost and calls `onDismiss`.
- `onRequest` is called after typing pauses; the component never requests on every keystroke.

### Edge cases
- A suggestion arriving after the person kept typing (`suggestionFor` no longer matches `value`): drop it silently, never paste stale text.
- Enter submits the message and never accepts a ghost.
- Cursor in the middle of existing text: show the ghost only if the parent supplies one for that position, without overlapping following text.
- IME composition in progress: hide the ghost until composition ends.

## Must avoid
Aggressive completions that fight the cursor, or accepting on a key the user presses for something else.

## Engineering constraint
Debounce on keystroke and cancel in-flight requests; latency budget under ~200ms or it reads as lag.

## Accessibility
- The ghost text is not part of the input value; expose it with `aria-describedby` hint text such as "Suggestion available, Tab to accept".
- Announce acceptance briefly; never announce each new suggestion.

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