Repository navigation
perf(main loop): input first, coalesce buffered keys, bracketed paste - #95
Conversation
The select was unbiased, so under a response burst roughly every other keystroke waited for a 64-action drain plus a redraw before it applied. Each key also cost a full frame, so a pasted address rendered one character at a time. - `biased;` select with the terminal arm first - drain input the terminal already buffered before redrawing (sync zero-timeout poll: a throwaway stream poll would register a dead waker) - enable bracketed paste; a paste lands in the search bar as one insert with one suggestion refresh, and is ignored outside search mode Refs #91 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
A critical UTF-8 cursor issue and two moderate input-handling issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Improves terminal responsiveness with input prioritization, buffered-event coalescing, and bracketed paste support.
Changes:
- Prioritizes and drains terminal input before redraws.
- Adds bracketed-paste handling and search insertion tests.
- Enables the crossterm bracketed-paste feature.
File summaries
| File | Summary |
|---|---|
src/main.rs |
Input dispatch and draining changes; moderate concerns about response starvation and stale errors after paste. |
src/app/input.rs |
Paste insertion logic and tests; critical UTF-8 cursor-boundary issue. |
Cargo.toml |
Enables bracketed-paste support. |
Review details
Suppressed comments (2)
src/main.rs:478
- Putting the terminal arm first makes response processing unboundedly postponable: a held key, continuous mouse-motion events, or another continuously readable input source can keep
event_stream.next()ready on every iteration, so the response arm is never selected. The 256-event drain only bounds one batch; add a fairness/budget point that services pending responses after an input burst if background updates must not starve.
tokio::select! {
// Input first: a pending key must never queue behind a response burst.
biased;
src/main.rs:540
- Paste is now the fast path for characters that previously arrived as
Keyevents, but this branch bypasses theapp.error_message = Noneperformed for every key. After a failed request, pasting into search leaves the stale error visible until another key is pressed; clear the error here to preserve the previous typing behavior.
app::input::handle_paste(app, &text);
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Left, Right, and Backspace moved `search_cursor` by one byte, so after a non-ASCII key or paste the next insert or remove hit a non-boundary index and panicked. Step by whole chars instead, and derive the terminal cursor column from the prefix's display width rather than its byte length. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical input-starvation and moderate stale-error findings must be addressed before approval.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
src/main.rs:541
- A paste in Search mode does not clear
app.error_message, unlike the key path immediately above. If an asyncAction::Errorarrives while the user is already searching, the pasted query updates the input but the stale error still takes priority instatus_bar::draw, hiding the normal status; clear the error for search pastes before dispatching.
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Lite
With the biased select an input event pending at every iteration would never let the response arm run. Take one pending response after handling input so responses always progress while input keeps priority. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Paste handling in src/main.rs and src/app/input.rs does not clear stale error messages.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
src/app/input.rs:529
- Typing a character clears
app.error_messageinhandle_terminal_event, but this paste path does not. If anAction::Errorarrives while search mode is open, pasting leaves the stale error visible even though typing the same text would clear it; clear the error beforesearch_insert(the early return already keeps normal-mode paste ignored).
src/main.rs:545
- Unlike typed search input, a bracketed paste never clears
app.error_messagehere. If a background response sets an error while search mode is open, the paste updates the query but leaves the stale error status visible until another key event; clear it for the same user action before delegating tohandle_paste.
Event::Paste(text) => {
debug!(len = text.len(), "Applying paste");
app::input::handle_paste(app, &text);
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Lite
Closes #91
Problem
The main loop's
tokio::select!was unbiased, so when a key and a network response were both pending, tokio picked one at random. While monitoring a busy contract there is almost always a response pending, so roughly every other keystroke waited for a 64-action drain plus a redraw before it applied. Bracketed paste was not enabled either, so a pasted address arrived as one key event per character, each costing a full frame.Change
biased;select with the terminal arm first. Responses cannot starve because key input is finite and bursty.EventStreamregisters the task waker only when it spawns its wake task, so a throwaway poll on the stream would capture a dead waker and stall input until the next network response.Event::Paste. In search mode the whole text is inserted at the cursor with one suggestion refresh; control characters such as a trailing newline are dropped. Outside search mode a paste is ignored, where previously each pasted character ran as a normal-mode command.handle_terminal_eventshared by the stream arm and the drain.Behavior note
Pasting text with a trailing newline used to submit the search immediately, because the newline arrived as an Enter key. The newline is now stripped and Enter is pressed explicitly.
Verification
cargo test: 4 new unit tests insrc/app/input.rscover paste at the cursor, control-char stripping, normal-mode ignore, and parity with typing the same characters.Applying paste len=66) and were visible in the search bar within 20 ms idle and within 10 ms while the address view was streaming. Enter resolved the address, typed input still produced suggestions, Ctrl+C exited cleanly.Follow-up
#92 attacks the same symptom from the other side by making each WebSocket-driven action cheap on large Calls lists.
🤖 Generated with Claude Code