fix(tui): paste-Enter must not auto-submit (#1073) + PTY QA harness

Two pieces:

**#1073 fix.** When a paste burst is currently being assembled, or when
the burst's Enter-suppression window is still open after a flush, the
trailing newline of the paste was firing `submit_input()` and the
in-flight burst buffer was getting destroyed by `clear_after_explicit_paste()`.
The PasteBurst module already exposed `newline_should_insert_instead_of_submit`
and `append_newline_if_active` for exactly this case, but no caller had
been wired up. Added `App::handle_composer_enter`, which checks the
suppression state and either appends `\n` to the burst buffer or inserts
it directly into the composer text — no submit. The `KeyCode::Enter`
arm in the composer event loop now dispatches through that helper.
Reproduces the Windows/PowerShell symptom from the report:
multi-line paste ending with `\n` no longer auto-submits AND the text
no longer leaks into the now-empty composer.

Four unit tests cover: active-burst Enter, post-flush window Enter,
normal Enter outside the window, and Enter with paste-burst detection
disabled (suppression must be off).

**PTY QA harness.** New `crates/tui/tests/support/qa_harness/` wraps
`portable-pty` (already a runtime dep) and `vt100` (new dev-dep) into
a small surface for scenarios that need a real PTY: spawn a binary,
send keys/paste/resize, parse the ANSI stream into a frame, assert
on visible text + filesystem state. The harness seals `$HOME` so
scenarios cannot read the developer's real `~/.deepseek/` and points
the base URL at 127.0.0.1:1 so no live request escapes. README under
`support/qa_harness/README.md` documents how to add a scenario.

Initial scenarios in `crates/tui/tests/qa_pty.rs`: smoke boot,
keystroke round-trip, and bracketed/unbracketed paste-with-trailing-
newline regression guards for #1073. The unbracketed scenario does
not deterministically reproduce the bug on macOS (single-syscall
PTY writes keep the burst continuously active), but the unit tests
above cover the path conclusively; the PTY test stands as a
regression guard for the visible-text invariant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hunter Bown
2026-05-07 12:23:57 -05:00
parent 22442d58b1
commit 4e285595b0
11 changed files with 1148 additions and 1 deletions
Generated
+40
View File
@@ -185,6 +185,12 @@ dependencies = [
"x11rb",
]
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "ascii"
version = "1.1.0"
@@ -1270,6 +1276,7 @@ dependencies = [
"unicode-segmentation",
"unicode-width 0.2.0",
"uuid",
"vt100",
"wait-timeout",
"windows",
"wiremock",
@@ -5297,6 +5304,39 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
[[package]]
name = "vt100"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84cd863bf0db7e392ba3bd04994be3473491b31e66340672af5d11943c6274de"
dependencies = [
"itoa",
"log",
"unicode-width 0.1.14",
"vte",
]
[[package]]
name = "vte"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197"
dependencies = [
"arrayvec",
"utf8parse",
"vte_generate_state_changes",
]
[[package]]
name = "vte_generate_state_changes"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "wait-timeout"
version = "0.2.1"