Raw unbracketed paste data may span adjacent stdin reads. The prior call-local check drained a first chunk such as "line 1\r" before later lines could classify the burst, leaving the original per-line submit bug.
- Held ESC-free break-bearing input in a fixed 10 ms classification window and appended adjacent raw reads before classification.
- Coalesced candidates after two completed logical line breaks; replayed ambiguous candidates unchanged through the normal per-key path on expiry or before escape-bearing input.
- Cleared and exposed pending candidates through the existing flush, clear, getBuffer, and destroy lifecycle.
- Added regressions for split reads, a break-only boundary, and delayed replay of ordinary Enter input.
Fixes#5841
The single-interior-break heuristic misclassified a single Enter batched with a following keystroke in one stdin read ("a\rb") as a paste, swallowing the submit. Terminal read boundaries are not key boundaries.
- Tightened RAW_MULTILINE_BURST to content-break-content-break-content so only 3+ line bursts (two interior break runs; CRLF counts as one) coalesce; a single Enter can never produce two interior breaks.
- Added regression tests for CRLF three-line bursts, a batched single Enter, and an ambiguous two-line burst staying on the key path.
Fixes#5841
- Detected an ESC-free stdin burst with an interior CR/LF in StdinBuffer.process and routed it through the paste channel instead of per-key splitting, so a Cmd+V multiline block whose terminal omitted the \x1b[200~…\x1b[201~ markers no longer fires one submit per line.
- Guarded to ESC-free buffers so real bracketed pastes, CSI keys, and mouse reports keep their path; a lone Enter, a single trailing newline, and a run of bare Enters stay normal keypresses.
- Added regression tests for the raw CR/LF burst, single Enter, trailing Enter, bare-Enter run, single-line burst, and escape-bearing chunk.
Fixes#5841
- Replaced unbounded `String#indexOf` lookups with structured byte loops to enforce `MAX_STRING_SEQ_BYTES` cap.
- Stopped unterminated OSC, DCS, and APC sequences from scanning past their limits and blocking the event loop.
- Maintained a one-byte overlap when checking chunk boundaries to ensure split escape terminators are correctly reassembled.
- Added comprehensive unit tests covering split boundary terminators and oversized payload scenarios.
Three failure modes shared the same root cause on the StdinBuffer.process
hot path — unbounded synchronous work in a single call.
- `StdinBuffer.extractCompleteSequences` grew each escape candidate one code
unit at a time and re-tested every prefix, so a malformed CSI/OSC/DCS/APC
streamed across chunks re-inspected the accumulated buffer on every
`process()` call (issue A). Replaced the grow-and-recheck loop with a
single linear scan bounded by a per-type cap (CSI 4 KiB, OSC/DCS/APC 16
MiB) and a resume-search offset carried on `StdinBuffer` so a legit
chunked OSC 5522 image paste stays O(total) instead of O(total²).
- `BracketedPasteHandler` had no cap or timeout: a caller that bypassed
StdinBuffer (fed the start marker with no end marker) grew `#buffer`
forever (issue B, defense in depth). Added an optional `byteLimit` option
(default 64 MiB) that aborts paste mode and delivers accumulated bytes as
`pasteContent`, mirroring `StdinBuffer#abortPaste`.
- The `ProcessTerminal` data handler ran six always-executed escape-probe
regex tests per `data` event; a 100 KB non-bracketed paste (terminal
without DEC 2004 support) turned into ~600K regex executions (issue C).
Added a fast path that forwards non-ESC sequences straight to the input
handler when no reassembly buffer is holding state.
Verified with new stdin-buffer + bracketed-paste tests plus empirical
repros: a streamed 1 MiB malformed CSI drops from ~4300 ms to ~50 ms of
synchronous work, and a 1 MiB chunked OSC 5522 payload drops from ~6800 ms
to ~25 ms.
Fixes#4073
StdinBuffer held a bare `\x1b\x1b` chunk and timer-flushed it as one
sequence. `parseKey("\x1b\x1b")` returns undefined, so CustomEditor
fell through to the base editor and never fired the configured `onEscape` —
the double-escape gesture and the second-press single-Esc handler both went
dead whenever the terminal batched the two presses into one stdin read.
Split an exact bare `\x1b\x1b` into two ESC events only after the
flush window proves no follower arrived. If a follower does arrive, emit the
first ESC and restart parsing at the second ESC so legacy Alt chords
(`\x1bd`, `\x1b\x7f`) remain one downstream keypress. Meta-CSI/SS3
chords (`\x1b\x1b[A`, `\x1b\x1bO…`) still emit as one combined
sequence.
EventController.tool_execution_update re-armed the working loader when a
transient overlay (auto-compaction / auto-retry / handoff) had torn it down
mid-tool; tool_execution_end did not. A subagent (`task`) call only fires
_end, so a task result landing after such an overlay left the UI looking
idle even though the session was still streaming. Mirror the reconciler
call in #handleToolExecutionEnd.
Fixes#3857
StdinBuffer held a bare `\x1b\x1b` chunk (or emitted it as one when followed by
a non-CSI byte). `parseKey("\x1b\x1b")` returns undefined, so CustomEditor
fell through to the base editor and never fired the configured `onEscape` —
the double-escape gesture and the second-press single-Esc handler both went
dead whenever the terminal batched the two presses into one stdin read.
Split a bare `\x1b\x1b` into two ESC events at the buffer layer, mirroring
the existing split for ESC + SGR mouse report. Meta-CSI/SS3 chords
(`\x1b\x1b[A`, `\x1b\x1bO…`) still emit as one combined sequence.
EventController.tool_execution_update re-armed the working loader when a
transient overlay (auto-compaction / auto-retry / handoff) had torn it down
mid-tool; tool_execution_end did not. A subagent (`task`) call only fires
_end, so a task result landing after such an overlay left the UI looking
idle even though the session was still streaming. Mirror the reconciler
call in #handleToolExecutionEnd.
Fixes#3857
- Stored the active kitty keyboard enable sequence in the terminal and re-applied it after entering the alternate screen, then emitted `\x1b[<u` before leaving the alternate screen.
- Updated StdinBuffer parsing so `\x1b\x1b` waits for CSI/SS3 followers, splits `\x1b\x1b[` SGR mouse reports into a standalone Esc plus report, and reduced `PARTIAL_HOLD_MAX_MS` to 150ms.
- Ensured flattened chain rows under a `└─` branch are anchored by a vertical line one level right of the suppressed gutter (below the branch head's content), never in the `└─` corner column itself, resolving visual issues #2298 and #2325.
Fixes#2325.
- Added `partialHoldTimeout` to `StdinBufferOptions` to configure extra wait for dangling partial escapes.
- Reworked `StdinBuffer` flush scheduling to defer emits and avoid stale partials merging with new escapes.
- Held SGR mouse and kitty partial escapes until cap expiry to prevent tail bytes leaking as typed text.
emergency restore leaves the alt screen and disables mouse tracking; bracketed paste gets an inactivity watchdog and byte cap so a lost end marker cannot eat input forever; split-escape flush window raised to 50ms; kitty printable dedup expires; resetDisplay repaints on the alt screen; input scanning is index-based instead of O(n^2) slicing; appearance poll no longer clears selection every 2s.
- Reworked `StdinBuffer` to collect bracketed-paste chunks incrementally and flush them once the end marker is found, while carrying an overlap tail so split markers are still detected.
- Simplified pasted-text sanitization in `Editor` to use a single regex pass that strips control characters while preserving newlines.
- Added tests covering split-marker boundaries and large chunked pastes to verify the new paste assembly behavior.
- converted relative imports to path aliases ($c/*, $ai/*, $tui/*, etc.) across all packages
- added per-package tsconfig.json with complete path mappings for runtime resolution
- set importModuleSpecifier to non-relative for IDE auto-import preferences
- updated dev script to run from monorepo root for consistent path resolution
- Created new @oh-my-pi/pi-utils workspace package with shared utilities for logging, process management, stream handling, and temporary directory management.
- Migrated all packages to use centralized logger from @oh-my-pi/pi-utils instead of local winston implementations.
- Replaced custom process spawning and stream reading implementations with standardized cspawn and readLines utilities across all modules.
- Converted synchronous file operations and process spawning to async patterns using Bun shell syntax and fs/promises.
- Added streaming edit abort functionality with configurable setting to abort on patch preview failures.
- Updated test framework from vitest to bun:test across all test suites.