[GH-ISSUE #748] High-frequency scroll events cause input starvation and dropped keyboard data #202

Open
opened 2026-03-02 23:45:14 +03:00 by kerem · 0 comments
Owner

Originally created by @androolloyd on GitHub (Feb 27, 2026).
Original GitHub issue: https://github.com/anomalyco/opentui/issues/748

Bug

When using a mouse with a free-spinning scroll wheel (e.g. Logitech MX Master 3 with ratchet unlocked), scrolling in any opentui-based app can freeze keyboard input entirely. On Ghostty, raw SGR escape sequences like \e[<65;93;34M also appear as visible text.

Root cause

Two issues in packages/core/src/renderer.ts:

1. stdinListener drops non-mouse data from mixed chunks

private stdinListener = ((data: Buffer) => {
    if (this._useMouse && this.handleMouseData(data)) {
        return  // ← entire buffer consumed for mouse events; remainder is lost
    }
    this._stdinBuffer.process(data)
})

When parseAllMouseEvents encounters non-mouse data (keyboard input, focus-in/out sequences, etc.) after mouse events in the same stdin chunk, it stops parsing. But handleMouseData returns true and stdinListener returns early — the non-mouse remainder is silently dropped.

This means keyboard input, focus-in (\e[I), and other control sequences that arrive in the same OS read as mouse data are permanently lost.

2. No scroll event coalescing

if (mouseEvent.type === "scroll") {
    const maybeRenderableId = this.hitTest(mouseEvent.x, mouseEvent.y)
    // ... immediately dispatch to renderable
}

Every scroll event is dispatched individually with no throttling or batching. Free-spinning wheels generate hundreds of events per second. Each event triggers hit testing → MouseEvent construction → processMouseEvent → Solid reactivity → layout. This saturates the Node.js event loop, starving keyboard event processing.

Reproduction

  1. Use a Logitech MX Master 3 (or any mouse with a free-spinning scroll wheel)
  2. Unlock the scroll wheel ratchet
  3. Open any opentui-based app (e.g. OpenCode)
  4. Scroll quickly or free-spin the wheel
  5. Try typing — keyboard input is delayed or entirely lost
  6. On Ghostty: raw SGR sequences appear as visible text in the terminal

Environment

  • opentui v0.1.84 (also confirmed on v0.1.73 bundled with OpenCode)
  • macOS, Ghostty and Terminal.app
  • Logitech MX Master 3 with free-spin wheel

Fix

PR forthcoming with:

  • parseAllMouseEvents returns consumed byte count; stdinListener forwards remainder to StdinBuffer
  • Consecutive same-direction scroll events within a chunk are coalesced into a single event with accumulated delta
Originally created by @androolloyd on GitHub (Feb 27, 2026). Original GitHub issue: https://github.com/anomalyco/opentui/issues/748 ## Bug When using a mouse with a free-spinning scroll wheel (e.g. Logitech MX Master 3 with ratchet unlocked), scrolling in any opentui-based app can freeze keyboard input entirely. On Ghostty, raw SGR escape sequences like `\e[<65;93;34M` also appear as visible text. ## Root cause Two issues in `packages/core/src/renderer.ts`: ### 1. `stdinListener` drops non-mouse data from mixed chunks ```ts private stdinListener = ((data: Buffer) => { if (this._useMouse && this.handleMouseData(data)) { return // ← entire buffer consumed for mouse events; remainder is lost } this._stdinBuffer.process(data) }) ``` When `parseAllMouseEvents` encounters non-mouse data (keyboard input, focus-in/out sequences, etc.) after mouse events in the same stdin chunk, it stops parsing. But `handleMouseData` returns `true` and `stdinListener` returns early — the non-mouse remainder is **silently dropped**. This means keyboard input, focus-in (`\e[I`), and other control sequences that arrive in the same OS read as mouse data are permanently lost. ### 2. No scroll event coalescing ```ts if (mouseEvent.type === "scroll") { const maybeRenderableId = this.hitTest(mouseEvent.x, mouseEvent.y) // ... immediately dispatch to renderable } ``` Every scroll event is dispatched individually with no throttling or batching. Free-spinning wheels generate hundreds of events per second. Each event triggers hit testing → MouseEvent construction → `processMouseEvent` → Solid reactivity → layout. This saturates the Node.js event loop, starving keyboard event processing. ## Reproduction 1. Use a Logitech MX Master 3 (or any mouse with a free-spinning scroll wheel) 2. Unlock the scroll wheel ratchet 3. Open any opentui-based app (e.g. OpenCode) 4. Scroll quickly or free-spin the wheel 5. Try typing — keyboard input is delayed or entirely lost 6. On Ghostty: raw SGR sequences appear as visible text in the terminal ## Environment - opentui v0.1.84 (also confirmed on v0.1.73 bundled with OpenCode) - macOS, Ghostty and Terminal.app - Logitech MX Master 3 with free-spin wheel ## Fix PR forthcoming with: - `parseAllMouseEvents` returns consumed byte count; `stdinListener` forwards remainder to `StdinBuffer` - Consecutive same-direction scroll events within a chunk are coalesced into a single event with accumulated `delta`
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/opentui#202
No description provided.