Header/title duplicates repeatedly when the terminal is narrowed
Summary
When the terminal window is made narrower, the top chrome of the TUI (the ✻ Claude title, the Code / Chat / Scheduled tab row, and the / search… line) gets redrawn on top of the old frame instead of replacing it, so it stacks up multiple copies. Dragging a window/pane smaller (which fires many resize events in a row) produces several duplicated header blocks; the more you shrink, the more copies pile up.
Steps to reproduce
- Run
claude-sessions in a terminal with at least one project that has sessions.
- Grab the window (or tmux pane) edge and drag it narrower, or run
resize-window/SIGWINCH down to ~24–40 columns.
- Observe the header/title/tab/search block appearing two or more times, stacked vertically.
Reproduced on main (v2.5.0), Node 22/24, inside a clean Fedora container, rendering through tmux 3.7 (a faithful terminal emulator). Not tmux-specific — any terminal reflows wrapped lines the same way.
Expected vs actual
- Expected: on resize the UI redraws once, cleanly, at the new width.
- Actual: each narrowing step leaves the previous header rows on screen and draws a fresh copy below them.
Evidence
Likely cause
Ink erases the previous frame by counting the number of logical lines it last emitted, then redraws. When the terminal gets narrower, lines that previously fit on one row now wrap onto several physical rows in the terminal. Ink's erase count no longer matches the physical rows the old frame actually occupies, so the stale (wrapped) rows are not cleared and the new frame is painted underneath them. Every SIGWINCH during a drag repeats this, so the header multiplies.
The app renders a fixed-width outer box (<Box … width={stdout.columns}> in src/index.tsx) and relies on Ink's built-in re-render on resize; there is no explicit resize/clear handling, which is what lets the artifact accumulate.
Fix estimate — Low (small, localized change)
This is a well-known Ink resize class of bug and the fix is a few lines in src/index.tsx, no architectural change:
- Add a
stdout "resize" handler that clears the screen (e.g. write \x1b[2J\x1b[3J\x1b[H) before Ink repaints, or
- Render into the alternate screen buffer (fullscreen mode) so reflow can't leave scrollback residue.
Main cost is verification across a couple of terminals (native terminal, tmux) and both grow/shrink directions — call it a half-day including testing.
I'm happy to open a PR implementing the resize-clear (or fullscreen) approach with a short before/after check across terminals — just let me know which direction you'd prefer.
Header/title duplicates repeatedly when the terminal is narrowed
Summary
When the terminal window is made narrower, the top chrome of the TUI (the
✻ Claudetitle, theCode / Chat / Scheduledtab row, and the/ search…line) gets redrawn on top of the old frame instead of replacing it, so it stacks up multiple copies. Dragging a window/pane smaller (which fires many resize events in a row) produces several duplicated header blocks; the more you shrink, the more copies pile up.Steps to reproduce
claude-sessionsin a terminal with at least one project that has sessions.resize-window/SIGWINCHdown to ~24–40 columns.Reproduced on
main(v2.5.0), Node 22/24, inside a clean Fedora container, rendering through tmux 3.7 (a faithful terminal emulator). Not tmux-specific — any terminal reflows wrapped lines the same way.Expected vs actual
Evidence
Likely cause
Ink erases the previous frame by counting the number of logical lines it last emitted, then redraws. When the terminal gets narrower, lines that previously fit on one row now wrap onto several physical rows in the terminal. Ink's erase count no longer matches the physical rows the old frame actually occupies, so the stale (wrapped) rows are not cleared and the new frame is painted underneath them. Every
SIGWINCHduring a drag repeats this, so the header multiplies.The app renders a fixed-width outer box (
<Box … width={stdout.columns}>insrc/index.tsx) and relies on Ink's built-in re-render on resize; there is no explicit resize/clear handling, which is what lets the artifact accumulate.Fix estimate — Low (small, localized change)
This is a well-known Ink resize class of bug and the fix is a few lines in
src/index.tsx, no architectural change:stdout"resize"handler that clears the screen (e.g. write\x1b[2J\x1b[3J\x1b[H) before Ink repaints, orMain cost is verification across a couple of terminals (native terminal, tmux) and both grow/shrink directions — call it a half-day including testing.
I'm happy to open a PR implementing the resize-clear (or fullscreen) approach with a short before/after check across terminals — just let me know which direction you'd prefer.