claude-code icon indicating copy to clipboard operation
claude-code copied to clipboard

Scroll region rendering broken in SwiftTerm after recent flickering fix

Open mihneadevries opened this issue 3 weeks ago • 6 comments

Description

After the recent flickering fix (mentioned as fixed ~Dec 18-19, 2025), Claude Code's TUI has broken scroll region behavior when viewed through SwiftTerm-based terminal emulators (iOS).

Symptoms

  • The bottom part of the terminal (status bar, prompt area) stays correctly in place
  • The top part (main content area) scrolls up one line on each terminal update
  • Content gets pushed off the top of the screen progressively
  • The scroll region (DECSTBM) seems to be set correctly, but something causes an extra scroll on each output

Visual

The terminal looks like Claude Code has set scroll regions correctly (status bar fixed at bottom), but every time new output arrives, the top region scrolls up one extra line, pushing content off screen.

Environment

  • Terminal emulator: SwiftTerm (iOS)
  • App: Xtro (iOS terminal client connecting to Mac)
  • Issue started: Around Dec 18-19, 2025 (coinciding with flickering fix)

Notes

  • Other TUI apps (vim, htop, etc.) work correctly - only Claude Code exhibits this behavior
  • Rotating the device (forcing terminal resize/redraw) used to fix the issue temporarily, but no longer does
  • The issue exists across multiple commits of our app, suggesting the change is on Claude Code's side

Workaround attempts

  • Terminal resize (SIGWINCH) - no longer fixes it
  • Different terminal sizes - same issue

Request

Could you check if the recent flickering fix changed how scroll regions (DECSTBM - ESC[top;bottomr) are being set or how the screen is being refreshed? Something in that change appears incompatible with SwiftTerm's scroll region handling.

mihneadevries avatar Dec 19 '25 06:12 mihneadevries

Just the same issue when working remotely using SSH Android client (Termius).

arkaitz-dev avatar Dec 19 '25 06:12 arkaitz-dev

Closing - the issue resolved after restarting. Apologies for the noise.

mihneadevries avatar Dec 19 '25 07:12 mihneadevries

Reopening - other users have reported similar scroll region issues. The bug may be intermittent or environment-specific. A restart temporarily resolved it on my end but the underlying issue may still exist.

mihneadevries avatar Dec 19 '25 07:12 mihneadevries

Update: Pinpointed the versions that introduced bugs

After testing version by version:

Version Scroll Region Bug Input Line Shift Bug
2.0.70 ✅ Works ✅ Works
2.0.71 Introduced ✅ Works
2.0.72 ❌ Persists Introduced
2.0.73 ❌ Persists ✅ Fixed

Bug 1: Scroll Region Bug (introduced in 2.0.71, still present)

Triggered when Claude Code shows an extra status row at the bottom:

  • ⏵⏵ accept edits on mode → scroll bug occurs
  • ⏸ plan mode on (shift+tab to cycle) mode → scroll bug occurs
  • Normal mode (no extra row) → works correctly

When that additional row is added, the scroll region (top portion) scrolls incorrectly - each update causes the top to scroll up by one line while the bottom status bar stays fixed.

Bug 2: Input Line Shift Bug (introduced in 2.0.72, fixed in 2.0.73)

In 2.0.72, the input line shifted down by one row, causing it to overlap with the horizontal delimiter lines in the prompt area. This was fixed in 2.0.73.


The scroll region bug appears to be related to how terminal height/scroll region boundaries are recalculated when the UI adds that extra status row. Something changed in 2.0.71 that breaks this behavior.

mihneadevries avatar Dec 19 '25 07:12 mihneadevries

Escape Sequence Analysis Update

I've implemented escape sequence logging in my terminal client to capture exactly what Claude Code is sending. Here are the detailed findings:

Test Environment

  • Terminal: 55 rows
  • Claude Code version: 2.0.73

Key Finding: Extra Erase Line (EL) Commands

Normal mode (no status bar):

=== BATCH: 1549 bytes | CUP:0 EL:15 ED:0 DECSTBM:0 ===

With 'accept edits' status bar:

=== BATCH: 1649 bytes | CUP:0 EL:16 ED:0 DECSTBM:0 [ACCEPT_EDITS] ===

The Problem

When the ⏵⏵ accept edits or ⏸ plan mode status bar appears at the bottom, Claude Code sends 1 extra EL (Erase Line) command per synchronized output batch (16 instead of 15).

What's Missing

Claude Code does NOT set a scroll region (DECSTBM / ESC[<top>;<bottom>r). Without a scroll region defined, the terminal treats the entire screen as scrollable. When the extra EL commands push content past the visible area, it causes the scroll.

Suggested Fix

When one more row in the status bar is added, Claude Code should either:

  1. Set a proper scroll region - e.g., ESC[1;54r for a XX-row terminal with one more status row
  2. Adjust the EL count - Send 15 EL commands instead of 16 when the status bar is present

Raw Log Samples

Before accept edits mode (16:50:55):

[16:50:55.551] === BATCH: 1548 bytes | CUP:0 EL:15 ED:0 DECSTBM:0 ===
[16:50:55.579] === BATCH: 1527 bytes | CUP:0 EL:15 ED:0 DECSTBM:0 ===
[16:50:55.966] === BATCH: 1538 bytes | CUP:0 EL:15 ED:0 DECSTBM:0 ===

After accept edits mode activates (16:50:57):

[16:50:57.466] === BATCH: 4739 bytes | CUP:0 EL:45 ED:0 DECSTBM:0 [ACCEPT_EDITS] ===
[16:50:57.494] === BATCH: 1649 bytes | CUP:0 EL:16 ED:0 DECSTBM:0 [ACCEPT_EDITS] ===
[16:50:57.502] === BATCH: 3298 bytes | CUP:0 EL:32 ED:0 DECSTBM:0 [ACCEPT_EDITS] ===
[16:50:57.518] === BATCH: 1649 bytes | CUP:0 EL:16 ED:0 DECSTBM:0 [ACCEPT_EDITS] ===

The pattern is consistent: batches that would have 15 EL now have 16 EL, batches with 30 EL now have 32 EL, etc.

mihneadevries avatar Dec 19 '25 09:12 mihneadevries

Ditto. Nothing to add other than my vote to fix this!

smconner avatar Dec 19 '25 10:12 smconner