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.

8
Components
6
Files
~2.4 k
Lines

📚 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 / FunctionFilePurpose
PromptInputPromptInput.tsxMain React component orchestrating the input area, mode switching, and autocomplete
useInputHistoryuseInputHistory.tsHook managing prompt history navigation with Up/Down arrow keys
SlashCommandAutocompletePromptInput.tsxInline autocomplete overlay showing matching slash commands as user types
ModeSelectorPromptInput.tsxDisplays current permission mode badge and handles Shift+Tab cycling
usePasteHandlerusePasteHandler.tsDetects and handles multi-line paste events, optionally confirming large pastes
useKeybindinghooks/useKeybinding.tsRegisters keyboard shortcuts (Ctrl+C, Escape, Enter) for the input context
handleSubmitPromptInput.tsxValidates and dispatches the user message to the conversation engine
handleKeyPressPromptInput.tsxCentral keypress router: Enter to submit, Shift+Enter for newlines, arrows for history
getCompletionsPromptInput.tsxGenerates filtered list of slash-command suggestions from the command registry
useExitOnCtrlCDhooks/useExitOnCtrlCD.tsDouble Ctrl+C / Ctrl+D exit handling with pending state indicator
TextInputink (external)Low-level Ink text input primitive providing cursor movement and character handling
cyclePermissionModeutils/permissionsCycles through permission modes on Shift+Tab keypress

Data Structures

TypeNameDescription
interfacePromptInputPropsProps including onSubmit, commands, isLoading, mode, and placeholder
typeInputHistoryEntryStored history item with text and timestamp
typeAutocompleteItemSlash command suggestion with name, description, and argHint
enumPermissionModeInput modes: default, plan, acceptEdits, bypassPermissions, auto
typeCommandRegistered slash command definition with name, description, run

Interactions

DirectionPartnerInterfaceDescription
→ConversationEngineonSubmit(text)Dispatches user message for processing by the AI pipeline
→PermissionContextcyclePermissionMode()Shift+Tab cycling through permission modes
←AppStateinputHistory[]Reads and writes prompt history from global state
→Command RegistrygetCompletions(text)Queries available slash commands for autocomplete
←REPLisLoadingDisables input while AI is responding
→AgentToolonSubmit("/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 / ParameterDefaultDescription
placeholder"Type a message…"Placeholder text shown when input is empty
maxHistorySize100Maximum number of prompts retained in history
multilineThreshold3 linesNumber of lines before input area expands vertically
pasteConfirmThreshold20 linesLine count above which paste requires user confirmation
autocompleteMaxItems8Maximum slash-command suggestions displayed at once
debounceMs50Input 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.

Extracted topic page

History Navigation Focus

Persistent prompt history with arrow-key navigation, deduplication, and state persistence across sessions.

Extracted topic page

Permission Mode Switching Focus

Shift+Tab cycling through permission modes (default → acceptEdits → plan → bypassPermissions) with badge display.

Extracted topic page

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

ScenarioBehavior
Empty submitSilently ignored (no-op)
Submit while loadingSilently ignored; input remains disabled
Unknown slash commandTreated as plain text and sent to the AI
Paste confirmation rejectedClipboard content discarded; input unchanged
History navigation at boundaryUp at oldest entry: no-op; Down at newest: restores current draft

Keyboard Shortcuts

KeyContextAction
EnterInput focusedSubmit prompt (if non-empty and not loading)
Shift+EnterInput focusedInsert newline (multi-line editing)
Up ArrowInput focused, cursor at line 1Navigate to previous history entry
Down ArrowInput focused, in historyNavigate to next history entry
EscapeInput focusedClear input text
Ctrl+CAnyCancel current operation or exit (double-press)
Ctrl+DAnyExit Claude Code (double-press)
Shift+TabInput focusedCycle permission mode (default → acceptEdits → plan → bypass)
TabInput focused, autocomplete visibleAccept 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 };
}