Skip to content

[BUG] Click/drag in a Screen's padding crashes with AssertionError (selection target is the Screen itself) #6596

Description

@bootloop-guillaume

The bug

A MouseDown (i.e. the start of a click or a drag-to-select) that lands in a screen's own padding/border gutter crashes the whole app with an AssertionError.

When the pointer is over the screen background rather than a child widget, Compositor.get_widget_and_offset_at() returns the Screen itself as the selection target. If the click also falls inside the screen's left gutter, the compositor takes its if x < 0: return widget, Offset(0, y) branch, so it returns the screen with a non-None offset. Screen._forward_event then enters the text-selection branch and runs:

content_widget = select_widget          # the Screen
assert isinstance(content_widget.parent, Widget)   # 💥

A Screen's .parent is the App, which is not a Widget, so the assertion fails and the app crashes.

The trigger is any Screen with horizontal/vertical padding (or a border) — extremely common — combined with a click or drag near the edge. It is independent of OS and terminal.

Minimal repro

Runs without modification. Click (or drag) starting in column 0 — the 1-cell left padding (select from half of the screen and drag to the left border):

from textual.app import App, ComposeResult
from textual.widgets import Static


class CrashApp(App):
    # Padding on the Screen creates a 1-cell gutter at column 0 that belongs to
    # the Screen itself — no child widget renders there.
    CSS = "Screen { padding: 0 1; }"

    def compose(self) -> ComposeResult:
        yield Static("Click or drag-select starting at the very left edge (column 0).")


if __name__ == "__main__":
    CrashApp().run()

Traceback

Traceback (most recent call last):
  File ".../site-packages/textual/app.py", line 4082, in on_event
    self.screen._forward_event(event)
  File ".../site-packages/textual/screen.py", line 1896, in _forward_event
    assert isinstance(content_widget.parent, Widget)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError

Probable cause / suggested fix

The assert in Screen._forward_event encodes the invariant "a selectable target always has a Widget parent", which is false for a Screen (its parent is the App). This looks like a sibling of the already-fixed #5634 / PR #5641, which added an if y < 0: return None, None guard in the same get_widget_and_offset_at; the x < 0 path was not given the equivalent treatment.

A minimal fix is to fold the parent check into the existing branch condition and let a screen-target fall through to the ay selection path:

if select_offset is not None and isinstance(select_widget.parent, Widget):
    content_widget = select_widget
    content_offset = select_offset
    container = content_widget.parent
else:
    content_widget = None
    container = select_widget       # the Screen — has .region / .scroll_offset
    content_offset = None

Happy to open a PR if this approach looks right.

textual diagnose

Versions

Name Value
Textual 8.2.7
Rich 14.3.4

Python

Name Value
Version 3.12.12
Implementation CPython
Compiler Clang 21.1.4

Operating System

Name Value
System Darwin
Release 25.5.0

Terminal

|-----------|----------------|
| Terminal | vscode |
| TERM | xterm-256color |
| COLORTERM | truecolor |


(Also reproduces in macOS Terminal.app and other terminals — the crash is in event handling, not rendering.)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions