💻 What REPL.tsx Does
The largest file in the codebase (~5,000 lines). Exports one function: REPL(props). Orchestrates conversation history, spinner, permission prompts, input box. Five internal layers: Props/Guards, State, Core Callbacks, Effects, Main Render.
💡 Why So Large
Genuine complexity: concurrency, permission queues, remote sessions, swarm workers, two render modes, session resume, keyboard navigation.
🔄 The Turn Lifecycle
onSubmit
Entry point. Handles immediate commands, idle-return gate, pending hooks, then routes to handlePromptSubmit.
onQuery
Concurrency guard via QueryGuard with generation counter. Stale finally blocks from cancelled queries don't corrupt state.
onQueryImpl
Actual work: builds system prompt, calls query(), streams response. Parallel async: system prompt + user context + killswitch checks.
💡 Ref Pattern
messages read via messagesRef.current (not closure) to keep onSubmit stable across ~30 setMessages calls per turn. Prevents memory leaks from pinned closure chains.
⏳ Loading State & Dialogs
Three independent loading sources: isQueryActive (local query), isExternalLoading (remote/SSH), hasRunningTeammates (swarm workers). Dialog priority via getFocusedInputDialog() - deterministic function returning single winner.
💡 Typing Suppression
Interrupt dialogs suppressed while user is typing. 1.5-second debounce resets after last keystroke.
💬 Messages Array
Not plain useState. Ref holds live value, state is render projection. Three consistency mechanisms: ephemeral progress replacement, compact boundary handling, deferred rendering via useDeferredValue.
💡 toolJSX Overlay
Local JSX commands cannot be overwritten by tool updates. In fullscreen, local-jsx renders in modal slot (absolute-positioned).
🔄 Session Resume
15-step sequence: deserialize messages, fire SessionEnd/Start hooks, copy plan slug, restore file history, restore agent definition, save/restore costs, switch sessionId, rename asciicast, clear/restore metadata, exit/enter worktree, reconstruct contentReplacementState.
💡 Why Clear Before Restore
restoreSessionMetadata only sets truthy fields. Without clear, resumed session would inherit previous session's cached values.
🎨 Render Tree & Optimizations
AlternateScreen > KeybindingSetup > FullscreenLayout with scrollable (Messages, spinner, toolJSX), bottom (PromptInput, dialogs), overlay (PermissionRequest), modal (local-jsx commands).
💡 AnimatedTerminalTitle Isolation
960ms spinner animation extracted to separate leaf component returning null. Only this tiny component re-renders on each tick, not the entire REPL tree.