Skip to content

inline mode: resize leaves stacked duplicate frames, and bounded region doesn't use available terminal height #6671

Description

@tya5

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):

  1. Run the app in a terminal.
  2. Resize the terminal window (e.g. narrower).
  3. Observe: a second complete copy of the bounded region appears below the first, both still visible.
  4. 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_resizesend_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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions