From 16f0ac2bdc49d4f1dc8823122082cdcf6c2b036d Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 3 Nov 2025 17:07:38 +0000 Subject: [PATCH 1/3] fix prune issue --- src/textual/_compositor.py | 4 ++-- src/textual/widget.py | 15 +++++++-------- tests/snapshot_tests/test_snapshots.py | 26 ++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index c4e240e2e9..9a6ea4c88e 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -615,8 +615,8 @@ def add_widget( ) ) capped_scroll_y = widget.validate_scroll_y(new_scroll_y) - widget.set_reactive(Widget.scroll_y, capped_scroll_y) - widget.set_reactive(Widget.scroll_target_y, capped_scroll_y) + widget.set_reactive(Widget.scroll_y, new_scroll_y) + widget.set_reactive(Widget.scroll_target_y, new_scroll_y) widget.vertical_scrollbar._reactive_position = new_scroll_y if visible_only: diff --git a/src/textual/widget.py b/src/textual/widget.py index f0b064fb27..4d6115ff97 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -4274,9 +4274,7 @@ def remove_children( ] else: children_to_remove = selector - await_remove = self.app._prune( - *children_to_remove, parent=cast(DOMNode, self._parent) - ) + await_remove = self.app._prune(*children_to_remove, parent=self) return await_remove @asynccontextmanager @@ -4423,11 +4421,12 @@ def _check_refresh(self) -> None: self.call_later(self._update_styles) if self._scroll_required: self._scroll_required = False - if self.styles.keyline[0] != "none": - # TODO: Feels like a hack - # Perhaps there should be an explicit mechanism for backgrounds to refresh when scrolled? - self._set_dirty() - screen.post_message(messages.UpdateScroll()) + if not self._layout_required: + if self.styles.keyline[0] != "none": + # TODO: Feels like a hack + # Perhaps there should be an explicit mechanism for backgrounds to refresh when scrolled? + self._set_dirty() + screen.post_message(messages.UpdateScroll()) if self._repaint_required: self._repaint_required = False if self.display: diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 1ace31a82c..0f99dbaf51 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -4713,3 +4713,29 @@ def compose(self) -> ComposeResult: yield Static("Hello, World! 293487 " * 200) assert snap_compare(ScrollbarApp()) + + +def test_prune_fix(snap_compare) -> None: + """Regression test for https://github.com/Textualize/textual/issues/6205 + + You should see the text "Hello" and "World" across the first two lines. + The original issue is that a layout operation wasn't done after removing children, leaving + a large gap between "Hello" and "World" + + """ + + class PruneApp(App): + BINDINGS = [Binding("c", "clear", priority=True)] + + def compose(self) -> ComposeResult: + yield Static("Hello") + with VerticalGroup(): + for i in range(10): + yield Static(str(i)) + yield Static("World") + + async def action_clear(self) -> None: + vs = self.query_one(VerticalGroup) + await vs.remove_children() + + assert snap_compare(PruneApp(), press=["c"]) From 7308d9fa9ea59e16d27785d1d776a2a340a68ae5 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 3 Nov 2025 17:08:31 +0000 Subject: [PATCH 2/3] no longer needed --- src/textual/_compositor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index 9a6ea4c88e..945d0841d4 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -614,7 +614,6 @@ def add_widget( - widget.scrollbar_size_horizontal ) ) - capped_scroll_y = widget.validate_scroll_y(new_scroll_y) widget.set_reactive(Widget.scroll_y, new_scroll_y) widget.set_reactive(Widget.scroll_target_y, new_scroll_y) widget.vertical_scrollbar._reactive_position = new_scroll_y From 60c4bffca3911d3658ae8377ea1ee1576be5bf44 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 3 Nov 2025 17:19:50 +0000 Subject: [PATCH 3/3] snapshit --- .../test_snapshots/test_prune_fix.svg | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 tests/snapshot_tests/__snapshots__/test_snapshots/test_prune_fix.svg diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_prune_fix.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_prune_fix.svg new file mode 100644 index 0000000000..d184e8372c --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_prune_fix.svg @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PruneApp + + + + + + + + + + Hello +World + + + + + + + + + + + + + + + + + + + + + + + + +