Skip to content

fix: capture bracketed paste so multi-line paste can't infinite-loop (#60)#70

Merged
yasha-dev1 merged 1 commit into
mainfrom
fix/bracketed-paste-infinite-loop
Jun 16, 2026
Merged

fix: capture bracketed paste so multi-line paste can't infinite-loop (#60)#70
yasha-dev1 merged 1 commit into
mainfrom
fix/bracketed-paste-infinite-loop

Conversation

@yasha-dev1

Copy link
Copy Markdown
Collaborator

Closes #60.

Problem

Pasting a long/multi-line block into the interactive REPL "cycled round and round" un-killably. Root cause: without bracketed-paste mode, the terminal sends the pasted text raw, and readline turns each pasted newline into an enter keypress → each fires a submit. A 20-line paste = 20 rapid submissions (each a new prompt / mid-run steer), which reads as an infinite loop that Ctrl+C/Esc can't catch between submissions.

Fix

Enable bracketed paste mode (ESC[?2004h) so the terminal wraps a paste in ESC[200~ … ESC[201~. Node's readline surfaces these as paste-start / paste-end keypresses (verified empirically). The textarea now:

  1. On paste-start, enters capture mode.
  2. Accumulates the literal characters in between (including \n) without handling them as keystrokes — so pasted newlines never submit.
  3. On paste-end, inserts the captured text via the same path as Ctrl+V (insertPaste): large/multi-line → a compact placeholder token from the Paste: store large multiline pastes internally and show a placeholder in the textarea #53 paste-store (expanded at submit); small single-line → inline.

A real Enter outside a paste still submits normally. Enable/disable is emitted on start/resume and torn down on pause/close (TTY only), and capture state is reset on pause so a half-captured paste can't strand the flag.

This composes with #53: bracketed pastes get the same placeholder treatment, so the input frame stays intact and the original newlines survive to submit.

Tests — packages/cli/tests/textarea-bracketed-paste.test.ts (8)

Behavioral (real createTextarea + fake stdin emitting the paste-start/content/paste-end sequence):

  • multi-line paste does NOT submit on its newlines (the bug) → one submit only on a later real Enter, with the full text preserved
  • single backspace atomically deletes the pasted placeholder
  • small single-line paste inlines
  • paste interleaved with typed text stays in order
  • a normal Enter (outside a paste) still submits
  • empty paste (start immediately end) doesn't strand capture state

TTY wiring:

  • enable (ESC[?2004h) on start, disable (ESC[?2004l) on close
  • disable on pause, re-enable on resume

Verification

  • Full suite: 694 passing, lint + typecheck clean (the Ctrl+V refactor to the shared insertPaste keeps all existing paste tests green).
  • Confirmed ESC[?2004h ships in the built dist bundle.

🤖 Generated with Claude Code

…60)

Pasting multi-line text into the REPL sent the pasted newlines through
readline as `enter` keypresses, each firing a submit — so a paste "cycled
round and round" as many rapid submissions that couldn't be interrupted.

Enable bracketed paste mode (ESC[?2004h) so the terminal wraps a paste in
ESC[200~ … ESC[201~; readline surfaces these as `paste-start`/`paste-end`.
We now capture everything between the markers as a single paste and route it
through the existing #53 paste-store (placeholder for large/multi-line,
inline for small), so pasted newlines never submit. A real Enter outside a
paste still submits.

- input.ts: paste-start/paste-end capture in onKeypress; shared insertPaste()
  (also used by Ctrl+V); enable on start/resume, disable on pause/close
  (TTY only); reset capture state on pause.

Tests (textarea-bracketed-paste.test.ts): multi-line paste does not submit on
its newlines (the bug), atomic-placeholder backspace, small paste inlines,
paste interleaved with typed text, normal Enter still submits, empty paste,
and enable/disable escape sequences emitted on start/close/pause/resume (TTY).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@yasha-dev1

Copy link
Copy Markdown
Collaborator Author

✅ Verification

Full suite + typecheck + lint (clean)

Test Files  80 passed (80)
     Tests  694 passed (694)
  • npm run typecheck → pass (core + cli)
  • npm run lint → pass

Paste tests (15; Ctrl+V refactor kept green + 8 new bracketed-paste)

✓ packages/cli/tests/textarea-paste.test.ts            (7 tests)
✓ packages/cli/tests/textarea-bracketed-paste.test.ts  (8 tests)

Root cause confirmed empirically

Feeding ESC[200~ab\ncd ESC[201~ through Node's readline.emitKeypressEvents:

names: ["paste-start","a","b","enter","c","d","paste-end"]

The pasted \n arrives as an enter keypress — which previously hit the submit path. The capture branch now collects it instead.

Behavior confirmed

  • A 3-line bracketed paste fires 0 submits during the paste; a later real Enter submits once with the full text (newlines preserved).
  • The pasted block is a single atomic placeholder (one backspace clears it).
  • Small single-line paste inlines; pasted + typed text interleave correctly; a normal Enter outside any paste still submits; empty paste is a no-op.
  • ESC[?2004h / ESC[?2004l are emitted on start/resume and close/pause (TTY), and ship in the built bundle.

This stops multi-line paste from firing a burst of submissions (the un-interruptible loop), while composing with #53's placeholder handling.

@yasha-dev1 yasha-dev1 added the PR OK Grok PR verified and review passed label Jun 16, 2026
@yasha-dev1

Copy link
Copy Markdown
Collaborator Author

PR OK — verified end-to-end (posted as comment; GitHub blocks self-approval).

Closes #60: the un-killable multi-line-paste loop. Root cause correctly diagnosed — without bracketed-paste mode each pasted newline becomes an enter keypress → a submit, so an N-line paste fires N rapid submissions that read as an infinite loop Ctrl+C/Esc can't catch between.

Fix verified:

  • Enables bracketed paste (ESC[?2004h); captures everything between paste-start/paste-end. The capture block sits early in onKeypress and returns before the submit handler, so no submit can fire mid-paste — that's what definitively breaks the loop and restores interruptibility. Confirmed by the does NOT submit on the newlines test (0 submits during paste, 1 on a later real Enter, full text preserved).
  • Captured text routes through the same insertPaste path as Ctrl+V → large/multi-line get the Paste: store large multiline pastes internally and show a placeholder in the textarea #53 placeholder, small inline. The Ctrl+V handler was refactored onto that shared helper; all prior paste tests stay green.
  • Lifecycle is correct: enable on init/resume, disable on pause/close (TTY-only, so the terminal isn't left in bracketed-paste mode on exit), and capture state is reset on pause so a half-captured paste can't strand the flag.
  • A normal Enter outside a paste still submits (tested).

Note: even if a given terminal delivered \r rather than \n for pasted newlines, the loop fix holds — the capture returns early regardless; newline-encoding only affects cosmetic preservation, and the common case is covered.

Verification: build ✓ · typecheck ✓ · lint ✓ · full suite 694/694 · 8 new behavioral tests (the bug, empty paste, interleaved typing, normal-Enter, atomic backspace, TTY enable/disable wiring) · ESC[?2004h confirmed in dist.

Severe bug, correctly fixed, well-tested, cleanly composed with the paste-store. LGTM.

@yasha-dev1 yasha-dev1 merged commit 314a20b into main Jun 16, 2026
2 checks passed
@yasha-dev1 yasha-dev1 deleted the fix/bracketed-paste-infinite-loop branch June 16, 2026 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR OK Grok PR verified and review passed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Infinity loop after user message

1 participant