components/PromptInput/ — User Input Component
The main text input where users type prompts and commands
Handles multi-line input, slash command autocomplete, history navigation, paste handling, and keyboard shortcuts. This is the primary human-to-AI interface surface in the Claude Code REPL, managing everything from simple text entry to complex mode switching and command dispatch.
📚 What is this & why does it exist?
PromptInput is the text field at the bottom of the Claude Code terminal UI where you type every message. It handles multi-line editing, remembers your previous prompts, shows slash-command suggestions as you type, and wires up keyboard shortcuts (Ctrl+C to cancel, Escape to clear, Up/Down to navigate history). Without it, there would be no way to communicate with the AI assistant.
💡 Real-world analogy
Think of PromptInput like the concierge desk at a hotel. You walk up and state your request (typing a prompt). The concierge remembers your previous requests (history navigation), suggests services as you start to speak (slash-command autocomplete), handles special instructions ("cancel that", "switch to plan mode"), and routes your request to the right department (mode switching). The desk itself adapts—expanding for longer requests, shrinking back for quick questions.
Contents
Architecture
flowchart TD PI["PromptInput.tsx\n(Main Component)"] --> TI["TextInput\n(Ink text field)"] PI --> AC["SlashCommandAutocomplete\n(Suggestion overlay)"] PI --> MS["ModeSelector\n(Permission mode)"] PI --> HN["useInputHistory\n(History hook)"] PI --> KS["useKeybinding\n(Shortcut wiring)"] PI --> PH["usePasteHandler\n(Paste detection)"] AC --> CMD["Command Registry\n(slash commands)"] MS --> PM["PermissionMode\n(utils/permissions)"] HN --> ST["AppState.inputHistory"]
Key Classes & Functions
| Class / Function | File | Purpose |
|---|---|---|
PromptInput | PromptInput.tsx | Main React component orchestrating the input area, mode switching, and autocomplete |
useInputHistory | useInputHistory.ts | Hook managing prompt history navigation with Up/Down arrow keys |
SlashCommandAutocomplete | PromptInput.tsx | Inline autocomplete overlay showing matching slash commands as user types |
ModeSelector | PromptInput.tsx | Displays current permission mode badge and handles Shift+Tab cycling |
usePasteHandler | usePasteHandler.ts | Detects and handles multi-line paste events, optionally confirming large pastes |
useKeybinding | hooks/useKeybinding.ts | Registers keyboard shortcuts (Ctrl+C, Escape, Enter) for the input context |
handleSubmit | PromptInput.tsx | Validates and dispatches the user message to the conversation engine |
handleKeyPress | PromptInput.tsx | Central keypress router: Enter to submit, Shift+Enter for newlines, arrows for history |
getCompletions | PromptInput.tsx | Generates filtered list of slash-command suggestions from the command registry |
useExitOnCtrlCD | hooks/useExitOnCtrlCD.ts | Double Ctrl+C / Ctrl+D exit handling with pending state indicator |
TextInput | ink (external) | Low-level Ink text input primitive providing cursor movement and character handling |
cyclePermissionMode | utils/permissions | Cycles through permission modes on Shift+Tab keypress |
Data Structures
| Type | Name | Description |
|---|---|---|
| interface | PromptInputProps | Props including onSubmit, commands, isLoading, mode, and placeholder |
| type | InputHistoryEntry | Stored history item with text and timestamp |
| type | AutocompleteItem | Slash command suggestion with name, description, and argHint |
| enum | PermissionMode | Input modes: default, plan, acceptEdits, bypassPermissions, auto |
| type | Command | Registered slash command definition with name, description, run |
Interactions
| Direction | Partner | Interface | Description |
|---|---|---|---|
| → | ConversationEngine | onSubmit(text) | Dispatches user message for processing by the AI pipeline |
| → | PermissionContext | cyclePermissionMode() | Shift+Tab cycling through permission modes |
| ← | AppState | inputHistory[] | Reads and writes prompt history from global state |
| → | Command Registry | getCompletions(text) | Queries available slash commands for autocomplete |
| ← | REPL | isLoading | Disables input while AI is responding |
| → | AgentTool | onSubmit("/agents") | Slash commands can trigger agent management flows |
Lifecycle Diagram
The PromptInput component follows a well-defined lifecycle from user keypress through to message dispatch. The sequence diagram below shows how the main interaction paths work together.
sequenceDiagram participant U as User participant PI as PromptInput participant AC as Autocomplete participant H as History participant E as Engine U->>PI: Types characters PI->>AC: getCompletions(text) AC-->>PI: Matching commands U->>PI: Presses Up arrow PI->>H: getPreviousEntry() H-->>PI: Previous prompt text U->>PI: Presses Enter PI->>PI: handleSubmit(text) PI->>H: pushEntry(text) PI->>E: onSubmit(text) E-->>PI: isLoading=true
Configuration
| Knob / Parameter | Default | Description |
|---|---|---|
placeholder | "Type a message…" | Placeholder text shown when input is empty |
maxHistorySize | 100 | Maximum number of prompts retained in history |
multilineThreshold | 3 lines | Number of lines before input area expands vertically |
pasteConfirmThreshold | 20 lines | Line count above which paste requires user confirmation |
autocompleteMaxItems | 8 | Maximum slash-command suggestions displayed at once |
debounceMs | 50 | Input debounce delay for autocomplete filtering |
Directory Map
Source tree for PromptInput
components/PromptInput/
├── PromptInput.tsx ← Main component: input field, autocomplete, mode selector
├── useInputHistory.ts ← Hook: prompt history navigation (Up/Down arrows)
├── usePasteHandler.ts ← Hook: multi-line paste detection and confirmation
├── types.ts ← Type definitions for props, history entries, completions
├── constants.ts ← Configuration defaults (thresholds, debounce)
└── index.ts ← Public barrel export
Deep-Dive Highlights
Core mechanisms from this sub-module have been extracted into dedicated focus pages for in-depth exploration. Each page covers one algorithm, protocol, or subsystem with diagrams, code walks, and configuration details.
Deep-Dive Pages
Slash Command Autocomplete Focus
How the command registry, fuzzy matching, and overlay rendering work together to provide inline suggestions.
History Navigation Focus
Persistent prompt history with arrow-key navigation, deduplication, and state persistence across sessions.
Permission Mode Switching Focus
Shift+Tab cycling through permission modes (default → acceptEdits → plan → bypassPermissions) with badge display.
Performance Notes
Input Debouncing
Autocomplete filtering is debounced at 50 ms to prevent excessive re-renders during fast typing. The completion list is pre-filtered on the main thread since the command registry is small (typically <30 commands), making web worker offloading unnecessary.
History Memory
The input history is capped at 100 entries and persisted to AppState.inputHistory. Entries are deduplicated—submitting the same prompt twice moves it to the top instead of creating a duplicate. The history array is stored as a simple JSON-serializable list with no circular references.
Paste Detection
Multi-line pastes are detected by checking if the input length increases by more than one character in a single event cycle. Pastes exceeding 20 lines trigger a confirmation dialog to prevent accidental submission of large clipboard contents. The paste handler also normalizes line endings (CRLF → LF) and strips trailing whitespace from each line.
Error Handling
| Scenario | Behavior |
|---|---|
| Empty submit | Silently ignored (no-op) |
| Submit while loading | Silently ignored; input remains disabled |
| Unknown slash command | Treated as plain text and sent to the AI |
| Paste confirmation rejected | Clipboard content discarded; input unchanged |
| History navigation at boundary | Up at oldest entry: no-op; Down at newest: restores current draft |
Keyboard Shortcuts
| Key | Context | Action |
|---|---|---|
Enter | Input focused | Submit prompt (if non-empty and not loading) |
Shift+Enter | Input focused | Insert newline (multi-line editing) |
Up Arrow | Input focused, cursor at line 1 | Navigate to previous history entry |
Down Arrow | Input focused, in history | Navigate to next history entry |
Escape | Input focused | Clear input text |
Ctrl+C | Any | Cancel current operation or exit (double-press) |
Ctrl+D | Any | Exit Claude Code (double-press) |
Shift+Tab | Input focused | Cycle permission mode (default → acceptEdits → plan → bypass) |
Tab | Input focused, autocomplete visible | Accept selected autocomplete suggestion |
Code Examples
Example: Handling Submit with History Push
// Inside PromptInput — simplified submit handler
const handleSubmit = useCallback((text: string) => {
if (!text.trim() || isLoading) return;
// Push to history before dispatching
inputHistory.push({ text, timestamp: Date.now() });
// Check for slash command prefix
if (text.startsWith('/')) {
const cmd = commands.find(c => text.startsWith('/' + c.name));
if (cmd) {
cmd.run(text.slice(cmd.name.length + 2).trim());
return;
}
}
onSubmit(text);
}, [isLoading, commands, onSubmit, inputHistory]);
Example: Autocomplete Filtering
// Slash command autocomplete with fuzzy prefix matching
function getCompletions(input: string, commands: Command[]): AutocompleteItem[] {
if (!input.startsWith('/')) return [];
const query = input.slice(1).toLowerCase();
return commands
.filter(cmd => cmd.name.toLowerCase().startsWith(query))
.slice(0, 8)
.map(cmd => ({
name: cmd.name,
description: cmd.description,
argHint: cmd.argHint ?? '',
}));
}
Example: History Navigation Hook
// useInputHistory hook — manages Up/Down arrow history navigation
function useInputHistory(history: InputHistoryEntry[]) {
const [historyIndex, setHistoryIndex] = useState(-1);
const [draft, setDraft] = useState('');
const navigateUp = useCallback(() => {
if (historyIndex < history.length - 1) {
if (historyIndex === -1) setDraft(currentInput);
setHistoryIndex(prev => prev + 1);
return history[historyIndex + 1]?.text ?? '';
}
return null; // At oldest entry
}, [historyIndex, history]);
const navigateDown = useCallback(() => {
if (historyIndex > 0) {
setHistoryIndex(prev => prev - 1);
return history[historyIndex - 1]?.text ?? '';
}
if (historyIndex === 0) {
setHistoryIndex(-1);
return draft; // Restore current draft
}
return null; // Not in history
}, [historyIndex, draft, history]);
return { navigateUp, navigateDown, resetHistory };
}