Skip to content

fix: prevent Chrome freeze and shell feedback delay from flicker filter#31

Closed
SGudbrandsson wants to merge 1 commit intoArk0N:masterfrom
SGudbrandsson:fix/flicker-filter-freeze
Closed

fix: prevent Chrome freeze and shell feedback delay from flicker filter#31
SGudbrandsson wants to merge 1 commit intoArk0N:masterfrom
SGudbrandsson:fix/flicker-filter-freeze

Conversation

@SGudbrandsson
Copy link
Contributor

Problem

Two related bugs in batchTerminalWrite's cursor-up flicker filter.

Bug 1 — Chrome tab freeze during active Claude sessions

The cursor-up flicker filter batches Ink's status bar redraws atomically: when a \x1b[A cursor-up sequence arrives, a 50ms flush timer is set, and all incoming data is buffered until the timer fires.

The bug: every incoming SSE terminal event reset the 50ms timer — not just cursor-up events. During an active Claude run the server emits terminal data continuously (tool calls, spinner updates, output), so the timer kept resetting and never fired. flickerFilterBuffer accumulated the full session output, potentially growing to several MBs over a long run. When Claude finally went idle, the 50ms timer fired and flushed everything to terminal.write() in a single synchronous call — freezing Chrome until the xterm.js parser finished.

Bug 2 — Shell mode shows no character feedback until Enter/pause

Shell sessions (bash/zsh with readline) emit cursor-up on every keystroke for prompt redraws (syntax highlighting, right-side prompts, etc.). The filter treated these the same as Ink status bar redraws and reset the 50ms timer on each character typed. Nothing appeared until the user stopped typing for 50ms, making the terminal feel completely unresponsive.

Fix

Bug 1: Only reset the 50ms flush timer when a cursor-up event arrives (the start of a new Ink redraw cycle). Non-cursor-up data that arrives while the filter is active is buffered without extending the deadline. Added a 256KB safety-valve to force-flush immediately if a burst fills the buffer before the timer fires.

Bug 2: Shell mode sessions (mode === 'shell') bypass the cursor-up filter entirely — there is no Ink status bar to protect in a plain shell. The local echo overlay is also disabled for shell sessions (it was looking for the Claude prompt character which doesn't exist in shell prompts; the shell handles its own PTY echo).

Changes

src/web/public/app.jsbatchTerminalWrite and _updateLocalEchoState:

  • hasCursorUpRedraw is now false for shell mode sessions
  • Timer reset moved inside if (hasCursorUpRedraw) guard; a fallback timer is set if none is running
  • 256KB force-flush safety valve added
  • _updateLocalEchoState adds a shell branch that clears and disables the overlay

Two related bugs in batchTerminalWrite's cursor-up flicker filter:

**Bug 1 — Chrome freeze during active Claude sessions**

The cursor-up flicker filter exists to batch Ink's status bar redraws
atomically. When a cursor-up sequence arrives, a 50ms flush timer is set.
The bug: every subsequent SSE terminal event — including non-cursor-up data
— also reset the 50ms timer. During an active Claude run, the server emits
terminal data faster than 50ms continuously, so the timer never fired.
flickerFilterBuffer accumulated the full session output (potentially MBs).
When Claude went idle, the timer fired and flushed everything to
terminal.write() in one synchronous call, freezing Chrome.

Fix: only reset the 50ms timer on cursor-up events (start of a new Ink
redraw cycle). Non-cursor-up data while the filter is active is buffered
without extending the deadline. Added a 256KB safety-valve to force-flush
if a burst fills the buffer before the timer fires.

**Bug 2 — Shell mode shows no character feedback until Enter**

Shell sessions (bash/zsh with readline) also emit cursor-up on every
keystroke for prompt redraws (syntax highlighting, right-side prompts).
The filter treated these as Ink status bar redraws and reset the 50ms
timer on each character typed, so nothing appeared until the user stopped
typing for 50ms — making the terminal feel completely unresponsive.

Fix: shell mode sessions bypass the cursor-up filter entirely (there is no
Ink status bar to protect). The local echo overlay is also disabled for
shell sessions since the shell handles its own PTY echo and the overlay
was looking for the ❯ Claude prompt character which doesn't exist in shell
prompts.
Ark0N added a commit that referenced this pull request Mar 4, 2026
Bug 1: Every incoming SSE terminal event reset the 50ms flush timer, not
just cursor-up events. During active Claude runs the timer never fired,
accumulating MBs in flickerFilterBuffer that froze Chrome on flush.
Fix: only reset timer on cursor-up events; add 256KB safety valve.

Bug 2: Shell sessions emit cursor-up on every keystroke for readline
prompt redraws, triggering the flicker filter and delaying feedback.
Fix: skip cursor-up filter for shell mode; disable local echo overlay.

Based on PR #31 by @SGudbrandsson.

Co-Authored-By: Sigurður Guðbrandsson <SGudbrandsson@users.noreply.github.com>
@Ark0N
Copy link
Owner

Ark0N commented Mar 4, 2026

Thanks for this excellent contribution, @SGudbrandsson! Both bugs were real and well-analyzed:

  1. Chrome freeze — the unconditional timer reset on every SSE event was a clear bug that would accumulate MBs during active sessions. Your fix to only reset on cursor-up events + the 256KB safety valve is clean.

  2. Shell mode feedback delay — great catch that shell readline's cursor-up for prompt redraws triggers the same filter. Excluding shell mode entirely is the right call since there's no Ink status bar to protect.

Applied your changes directly to master in 1b76e6e (with co-author credit). Closing this PR — thanks again for the thorough investigation!

@Ark0N Ark0N closed this Mar 4, 2026
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.

2 participants