Description
Two related bugs with App.run(inline=True) when the terminal is resized after the app has already rendered at least one frame, on macOS/iTerm2+tmux (real TTY, not run_test/headless):
Bug A — resize leaves a stacked duplicate frame instead of redrawing in place. After a resize, the app appears to print an entirely new frame below the previous one rather than clearing/repositioning to redraw over it. Repeated resizes stack more copies (2x, 3x, ...), cumulatively, never self-correcting.
Bug B — the inline region does not use available terminal height. With Screen having no explicit height set (relying on the documented "no explicit height → resolves up to app.size.height" behavior), the bounded inline region renders using only ~1 visible content line, even in a 45-row terminal — it does not appear to expand to use the available space.
Reproduction
Minimal app (no third-party widgets):
from textual.app import App, ComposeResult
from textual.widgets import RichLog, Input
class MinimalInlineApp(App):
CSS = """
Screen { layout: vertical; height: 15; }
RichLog { height: 1fr; }
Input { dock: bottom; }
"""
def compose(self) -> ComposeResult:
yield RichLog(highlight=False, markup=False)
yield Input(placeholder="type + Enter, ctrl+q to quit")
def on_mount(self) -> None:
self.query_one(RichLog).write("minimal inline app")
if __name__ == "__main__":
MinimalInlineApp().run(inline=True)
Steps (real terminal, tested via tmux resize-window, but any terminal resize should trigger the same SIGWINCH path):
- Run the app in a terminal.
- Resize the terminal window (e.g. narrower).
- Observe: a second complete copy of the bounded region appears below the first, both still visible.
- Resize again: a third copy appears. Cumulative, does not self-correct.
For Bug B, drop the explicit height: 15; (use Screen { layout: vertical; } with no height at all) and write more lines than fit in ~7 rows — even in a tall terminal (tested at 120x45), only the last line remains visible; the region never grows to use the extra space.
Investigation so far
Read textual/drivers/linux_inline_driver.py's start_application_mode: on SIGWINCH, on_terminal_resize → send_size_event(clear=True) → writes \x1b[2J (erase-in-display) then posts a Resize event — confirmed via a debug print that the SIGWINCH handler does fire on a real terminal resize (tmux resize-window), so the signal delivery isn't the gap.
Tried two candidate patches to linux_inline_driver.py (both reverted after testing, not proposing either as the fix — just ruling out the shallow explanations):
- Adding a cursor-home (
\x1b[2J\x1b[H instead of bare \x1b[2J) — did not eliminate the stacking.
- Adding an explicit
self.flush() immediately after the write, before _post_message — did not eliminate the stacking either.
Both still reproduce the duplicate-frame stacking, which suggests the gap isn't a missing terminal-side clear/flush, but likely a mismatch between the real terminal cursor position (which the raw escape write moves) and Textual's own internal notion of where the inline region currently is / how many lines it previously emitted — i.e. something in the compositor's paint logic for inline mode isn't accounting for cursor position drift across a resize, independent of whether the terminal was actually cleared first.
Versions
textual==8.2.8
- macOS (Apple Silicon), iTerm2/Terminal.app running tmux 3.x, real TTY (not
run_test/HeadlessDriver)
- Python 3.12
Happy to provide more detail / try further patches if useful — this surfaced while building an inline chat UI on top of textual + textual-flowview where resize-follow and bounded-height inline are both load-bearing requirements.
Description
Two related bugs with
App.run(inline=True)when the terminal is resized after the app has already rendered at least one frame, on macOS/iTerm2+tmux (real TTY, notrun_test/headless):Bug A — resize leaves a stacked duplicate frame instead of redrawing in place. After a resize, the app appears to print an entirely new frame below the previous one rather than clearing/repositioning to redraw over it. Repeated resizes stack more copies (2x, 3x, ...), cumulatively, never self-correcting.
Bug B — the inline region does not use available terminal height. With
Screenhaving no explicitheightset (relying on the documented "no explicit height → resolves up toapp.size.height" behavior), the bounded inline region renders using only ~1 visible content line, even in a 45-row terminal — it does not appear to expand to use the available space.Reproduction
Minimal app (no third-party widgets):
Steps (real terminal, tested via tmux
resize-window, but any terminal resize should trigger the sameSIGWINCHpath):For Bug B, drop the explicit
height: 15;(useScreen { layout: vertical; }with no height at all) and write more lines than fit in ~7 rows — even in a tall terminal (tested at 120x45), only the last line remains visible; the region never grows to use the extra space.Investigation so far
Read
textual/drivers/linux_inline_driver.py'sstart_application_mode: onSIGWINCH,on_terminal_resize→send_size_event(clear=True)→ writes\x1b[2J(erase-in-display) then posts aResizeevent — confirmed via a debug print that the SIGWINCH handler does fire on a real terminal resize (tmuxresize-window), so the signal delivery isn't the gap.Tried two candidate patches to
linux_inline_driver.py(both reverted after testing, not proposing either as the fix — just ruling out the shallow explanations):\x1b[2J\x1b[Hinstead of bare\x1b[2J) — did not eliminate the stacking.self.flush()immediately after the write, before_post_message— did not eliminate the stacking either.Both still reproduce the duplicate-frame stacking, which suggests the gap isn't a missing terminal-side clear/flush, but likely a mismatch between the real terminal cursor position (which the raw escape write moves) and Textual's own internal notion of where the inline region currently is / how many lines it previously emitted — i.e. something in the compositor's paint logic for inline mode isn't accounting for cursor position drift across a resize, independent of whether the terminal was actually cleared first.
Versions
textual==8.2.8run_test/HeadlessDriver)Happy to provide more detail / try further patches if useful — this surfaced while building an inline chat UI on top of
textual+textual-flowviewwhere resize-follow and bounded-height inline are both load-bearing requirements.