Problem
When a user pastes multi-line text into a TextInputNode, the terminal delivers pasted content character-by-character. Each \n in the pasted text triggers HandleEnter(), which fires the Submitted observable. This results in each line of the paste being treated as a separate submission.
Observed behavior in Netclaw TUI (netclaw chat):
- User pastes a long blog post into the input field
- Each CRLF in the pasted text fires
Submitted separately
- The app receives 50+ individual submissions instead of one
- The app becomes unresponsive because each submission triggers an LLM call
Ctrl+Q (quit) can't fire until all queued submissions drain through the event loop
Expected Behavior
Termina should detect bracketed paste mode from the terminal:
- Detect paste start — terminals that support bracketed paste mode send
ESC[200~ before pasted content and ESC[201~ after
- Buffer paste content — while inside a paste bracket, buffer all characters (including newlines) without triggering
Submitted
- Deliver as single event — when the paste bracket closes, either:
- Insert all pasted text into the
TextInputNode as a single string (without submitting), OR
- Fire a separate
Pasted observable with the full pasted text
- Fallback for terminals without bracketed paste — detect rapid sequential
Enter keystrokes (e.g., within 50ms of each other) and coalesce them. This is the same heuristic Claude Code and OpenCode use.
UX Reference
Claude Code and OpenCode both handle this well:
- They show "pasted text" as a summary/collapsible block
- The pasted content is treated as a single message, not line-by-line submissions
- The user can review the pasted content before submitting
Workaround
Applications can work around this today by debouncing Submitted:
_promptInput.Submitted
.Where(text => !string.IsNullOrWhiteSpace(text))
.Buffer(TimeSpan.FromMilliseconds(100))
.Where(batch => batch.Count > 0)
.Subscribe(batch =>
{
_promptInput.Clear();
var combined = string.Join("\n", batch);
// ... handle as single submission
});
This works but is application-level — the framework should handle it.
Technical Notes
TextInputNode.HandleInput() processes ConsoleKey.Enter via HandleEnter() which calls _submitted.OnNext(_text)
- The
Ctrl+V handler in HandleCharacter() currently returns true without doing anything (it's a no-op)
- Bracketed paste mode is widely supported: xterm, iTerm2, Windows Terminal, GNOME Terminal, Alacritty, kitty
- The terminal must be put into bracketed paste mode by sending
ESC[?2004h on startup and ESC[?2004l on shutdown
Problem
When a user pastes multi-line text into a
TextInputNode, the terminal delivers pasted content character-by-character. Each\nin the pasted text triggersHandleEnter(), which fires theSubmittedobservable. This results in each line of the paste being treated as a separate submission.Observed behavior in Netclaw TUI (
netclaw chat):SubmittedseparatelyCtrl+Q(quit) can't fire until all queued submissions drain through the event loopExpected Behavior
Termina should detect bracketed paste mode from the terminal:
ESC[200~before pasted content andESC[201~afterSubmittedTextInputNodeas a single string (without submitting), ORPastedobservable with the full pasted textEnterkeystrokes (e.g., within 50ms of each other) and coalesce them. This is the same heuristic Claude Code and OpenCode use.UX Reference
Claude Code and OpenCode both handle this well:
Workaround
Applications can work around this today by debouncing
Submitted:This works but is application-level — the framework should handle it.
Technical Notes
TextInputNode.HandleInput()processesConsoleKey.EnterviaHandleEnter()which calls_submitted.OnNext(_text)Ctrl+Vhandler inHandleCharacter()currently returnstruewithout doing anything (it's a no-op)ESC[?2004hon startup andESC[?2004lon shutdown