Skip to content

Preserve multiline pastes as one Sidekick message - #1063

Merged
taras merged 1 commit into
mainfrom
codex/sidekick-paste-1046
Nov 1, 2026
Merged

taras merged 1 commit into
mainfrom
codex/sidekick-paste-1046

Conversation

@taras

@taras taras commented Nov 1, 2026 •

Copy link
Copy Markdown
Owner

Pasting a multiline prompt into Sidekick leaves the complete text in one editable message. Blank lines, indentation and Unicode survive insertion at the caret. Paste starts no message, queued work or provider call; an explicit Send submits the entire draft once.

The CLI decoder consumes bracketed-paste framing before ordinary key scanning and normalizes CR/CRLF line breaks to LF. It preserves markers, UTF-8 and payloads split across reads, including pastes beyond one scanner pass. The existing screen owner enables and restores bracketed-paste mode. Deliberately typed submission keys and the ordinary Round draft keep their current behavior. Dispatch, queue, providers and public APIs are unchanged.

Validation: 38 focused test groups / 198 steps, Deno checks for the input/screen modules, and Node typechecking pass. Headless byte-to-editor/dispatch journeys cover idle and held-turn pastes, middle-caret insertion, zero premature work and one exact complete submission. Terminal mode setup/reset, every two-read marker/Unicode split, byte-by-byte input and long payloads are covered. This is terminal-independent injected-byte evidence; no physical clipboard or iTerm2 verification is claimed.

The original Planner passes signed commit d76e3b15, including the direct human clarification accepting headless terminal-path evidence. The one independent Fable review reports no blocking findings at this exact head. It traced escape/key compatibility, paste framing and mode cleanup, and measured the affected headless input/editor path. Required CI and current-main health are green.

A pre-existing loss of oversized unframed reads in the underlying tty scanner is documented in the review and remains outside this framed-paste fix. No correction was requested; a separate follow-up is prepared. No physical clipboard, iTerm2 or native compositor measurement is claimed.

Fixes #1046.

@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown

PR #1063: Preserve multiline pastes as one Sidekick message

6 files, +273 / -22

Scope

✅ PR scope looks good.

Structural

✅ No structural bloat detected.

Slop

✅ Slop indicators look low.

Static Analysis

Oxlint: 3 diagnostics across 1 file (1 rule)
Density: 0.011 violations/added-line

no-floating-promises (3): packages/cli/src/repl/screen.ts

Correctness

FILE: packages/cli/src/repl/screen.ts
PATTERN: no-floating-promises
CONCERN: Generator functions yielding without proper promise handling
QUESTION: Are unhandled generator yields in keys() intentional, or do they risk silent failures in async contexts?

FILE: packages/cli/src/repl/screen.ts
PATTERN: signal cluster
CONCERN: 3 no-floating-promises violations in same file suggest unhandled async operations
QUESTION: Are these generator-based yields properly consumed in all execution paths?

No other extraneous code patterns detected.

@taras taras left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent review — PR #1063 at d76e3b15

Verdict: approve (posted as a comment review — GitHub refuses an APPROVE event from the PR author's account, HTTP 422). No blocking findings. One non-blocking, pre-existing observation is left as an inline comment on packages/cli/src/repl/input.ts. Main health and the required CI checks remain merge gates; this review ran alongside them and reused the focused evidence (terminal, dispatch and presentation suites, Deno and Node typechecks) plus bounded measurements at this exact head.

Correctness

  • Traced the new prefix/paste state in packages/cli/src/repl/input.ts through the scanner loop in packages/cli/src/repl/screen.ts. A held lone ESC now reports escLatency itself, the flush feeds the held byte and settles it, so Escape, ESC|[A split arrows, kitty ESC[27u, the F9/Insert prefixes that share ESC[2… with the opening marker, Alt chords, Control-J and unframed CR decode identically to the parent commit. Probe of 13 sequences on both decoders: the only differences are the fixed leakage, where the parent emitted "2","0","0","~",…,Enter for a framed paste.
  • Payload bytes never reach the key scanner; CR/CRLF become LF; ordinary bytes before and after the frame keep their order (P3). A frame truncated at EOF drops its payload silently, matching the plan's "no new compatibility policy". Mode 2004 rides the existing Setting, so apply/revert ordering and the single reset are unchanged (P7 extends existing cases).
  • @bomb.sh/tty 0.9.0 exposes no paste event (esm/input.d.ts), so the hand-rolled framing is the smallest option. Its escLatency default is 25, so ?? 25 changes nothing.

Minimalism

Scope held: decoder, one screen setting, spec and architecture paragraphs; no new owner, API, dependency or test file. Optional nits only: the paste pass object is the one unfrozen ReplInputScan, and opening/closing could be module constants.

Performance (measured at this head)

Boundary: the per-read decoder loop and one text event per paste. Byte source: in-process injected host (terminal.bytes()), production screen decoder, field, dispatch, Termless xterm.js backend at 160x36. Not an OS PTY, native terminal or clipboard. "Writes" are completed ReplTerminal writes, not frames or paint.

workload result
decoder: 2000 typed reads (letters, ESC[A, \r), 1 warmup + 5 samples head p50 2.5 µs, p95 3.2 µs per read; parent p50 1.2 µs, p95 1.5 µs; same 2000 events
decoder: framed paste 1 KB / 16 KB / 262 KB, single, 4096- and 131-byte reads, CRLF and LF 0.13–0.34 ms / 0.7–1.6 ms / 7.7–11.7 ms; exactly one text event, exact text every case
mounted: 3.4 KB framed CRLF paste at a middle caret, reads of 1/17/509/1024/4095 bytes 42.7 ms bytes→tail visible, 1 completed write, 0 prompts, QUEUE 0/16
mounted: same content unframed LF (the per-character path every terminal used before this PR) 920.8 ms, 1 completed write, 0 prompts
mounted: 35 typed keystrokes p50 6.6 ms, p95 7.4 ms bytes→visible, 1 write each (head reference; no parent run)

Limitations: no CPU profile retained because decoder costs are microseconds to milliseconds and no hot stack was in doubt; the parent was compared at the decoder only, since the field/dispatch path is unchanged; single host, indicative numbers, no FPS budget implied.

ordinary.push(byte);
}
}
passes.push(yield* keys(Uint8Array.from(ordinary)));

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, pre-existing, outside this PR's accepted scope. Ordinary bytes still reach the tty scanner in one scan(bytes) call. @bomb.sh/tty 0.9.0 accepts at most SCAN_BUFFER_SIZE (4096) bytes per native pass and breaks when a chunk is not fully accepted, so one unframed read above 4 KB loses everything after the first ~4 KB; drain() recovers pending events, not unaccepted bytes.

Evidence, identical on the parent and this head: one 16,438-byte unframed LF read → 3,925 of 15,233 chars; one 65,586-byte read → 3,925 of 60,776; the same payload in 4,096-byte reads → 15,228 of 15,233 (multibyte sequences split at a read boundary are dropped by the scanner).

Consequence: only terminals without bracketed paste, or piped stdin, delivering more than 4 KB per read; real ttys usually deliver at most 4 KB. Framed pastes bypass this because the payload is buffered here, which is the fix for the reported case.

Recommendation: track in a follow-up issue rather than this PR. The smallest later repair is feeding ordinary to keys() in slices of at most 4096 bytes, since the per-byte loop already exists here. No change requested for #1063.

@taras
taras marked this pull request as ready for review November 1, 2026 21:42
@taras
taras merged commit 40a3635 into main Nov 1, 2026
58 checks passed
@taras
taras deleted the codex/sidekick-paste-1046 branch November 1, 2026 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Preserve multiline pastes as one Sidekick message

1 participant