Skip to content

TUI Ghost Content - Shell output persists when launching TUI apps #239

@kshivang

Description

@kshivang

Problem Description

When launching TUI applications (Claude Code, vim, htop) after running shell commands, old shell output remains visible at the bottom of the TUI screen, creating "ghost content".

Steps to Reproduce

# Fill screen with output
for i in {1..50}; do echo "Line $i"; done

# Launch TUI app
claude code

Expected: Clean TUI display
Actual: "Line 1", "Line 2", etc. visible at bottom of TUI interface

Investigation Summary

Initial Hypothesis (INCORRECT)

  • Thought it was related to alternate buffer switching
  • Reality: Claude Code doesn't use alternate buffer mode

Current Leading Theory: Double Redraw Race Condition

When TUI apps clear the screen (CSI 2J), the following sequence occurs:

T+0ms:   eraseInDisplay(mode=2) called
T+5ms:   terminalTextBuffer.batch { clearLines() }
         └─ Batch ends → fires ModelListener.modelChanged()
            └─ Calls display.requestRedraw() → REDRAW #1 (queued with debounce)
T+10ms:  myDisplay.forceRedraw() → REDRAW #2 (direct to Main dispatcher)
T+20ms:  RACE CONDITION - Two redraws executing with different buffer snapshots
         └─ INTERMEDIATE STATE RENDERS ← Potential cause of ghost content

Key files involved:

  • BossTerminal.kt:792-820 (mode 2 clear)
  • BossTerminal.kt:822-846 (mode 3 clear)
  • TerminalTextBuffer.kt:305-315 (endBatch fires ModelListener)
  • TabController.kt:289-294 (ModelListener calls requestRedraw)

Debug Logging Added

Temporary debug logging can be added to /tmp/bossterm-debug.log to trace:

  • When eraseInDisplay modes 0/1/2/3 execute
  • When forceRedraw() is called
  • When actualRedraw() increments _redrawTrigger
  • Redraw sequence timing

Attempted Fix (Reverted)

Added criticalBatch() method to suppress automatic ModelListener callbacks during mode 2/3 clears, leaving only the explicit forceRedraw(). This eliminated the double redraw but broke input handling - needs further investigation into why.

Additional Observations

  • Issue occurs specifically when TUI apps launch after shell output
  • Does not occur when launching TUI in clean terminal
  • May be related to synchronized update mode (OSC 2026) timing
  • Suppression mechanisms exist for partial erases (mode 0/1) but not full clears

Next Steps

  1. Investigate why suppressing automatic redraws broke input handling
  2. Consider if the race condition is actually the root cause or just a symptom
  3. Analyze TUI initialization sequence more carefully (setSynchronizedUpdate timing)
  4. Test with other TUI apps to see if behavior is consistent
  5. Consider if the issue is in the snapshot rendering mechanism instead

Related Code

  • Rendering: ComposeTerminalDisplay.kt (requestRedraw, forceRedraw, actualRedraw)
  • Buffer: TerminalTextBuffer.kt (batch operations, ModelListener)
  • Terminal: BossTerminal.kt (eraseInDisplay modes)
  • Snapshot: IncrementalSnapshotBuilder.kt (lock-free rendering)

Environment

  • Platform: Linux
  • Terminal: BossTerm
  • Test apps: Claude Code, vim, htop, any TUI that clears screen

Note: This is a complex rendering issue that requires careful investigation to avoid breaking existing functionality. The double redraw theory seems plausible but needs more validation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions