Skip to content

Fix container anchor re-engagement during continuous content streaming - #6694

Open
Jyotish08 wants to merge 2 commits into
Textualize:mainfrom
Jyotish08:main
Open

Fix container anchor re-engagement during continuous content streaming#6694
Jyotish08 wants to merge 2 commits into
Textualize:mainfrom
Jyotish08:main

Conversation

@Jyotish08

@Jyotish08 Jyotish08 commented Aug 9, 2026

Copy link
Copy Markdown

closes #6677 Problem
When Widget.anchor() is active on a scrollable container (e.g. VerticalScroll), any call to scroll_to(), scroll_relative(), scroll_by(), or scrollbar interaction sets release_anchor=True, placing the widget into _anchor_released = True.

While content is actively streaming (e.g. mounting child widgets continuously), max_scroll_y grows every frame. Re-engaging the anchor via _check_anchor() required scroll_y >= max_scroll_y exactly. Because max_scroll_y increases continuously while content is added, scroll_y lags behind max_scroll_y by at least 1 unit during scroll events, rendering exact equality unreachable. Consequently, once released, an anchored container would permanently remain unanchored and fail to follow new content even after scrolling back to the bottom.

Solution
Target position anchor restoration: Updated _check_anchor() in widget.py to evaluate self.scroll_target_y >= self.max_scroll_y alongside self.scroll_y >= self.max_scroll_y.
Bottom scroll anchor protection: In scroll_to() and _scroll_to(), when y >= self.max_scroll_y, release_anchor is suppressed and _anchor_released is reset to False (self._anchor_released = False), as scrolling to or beyond the bottom indicates an intent to stay at the bottom edge.
Compositor anchor check: In _compositor.py, widget._check_anchor() is now invoked immediately before evaluating if widget._anchored and not widget._anchor_released:, ensuring _anchor_released is cleared before bottom-gluing calculations occur during layout rendering.
Testing
Added regression test test_anchor_streaming_repro_flow in tests/test_anchor.py verifying that:
Scrolling away (y=-1) releases the anchor and keeps scroll_y frozen while items mount.
Scrolling back to the bottom (v.scroll_to(y=v.max_scroll_y)) restores the anchor and resumes gluing the viewport to new content as items mount.
Verified all core unit tests pass (pytest tests/test_widget.py tests/test_containers.py tests/test_auto_scroll.py tests/test_anchor.py).

Copilot AI lite review requested due to automatic review settings August 9, 2026 09:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts Textual’s anchoring behavior for scrollable containers so that anchors can be re-engaged reliably while content is continuously appended (streaming/mounting), and adds a regression test to cover the reported flow.

Changes:

  • Update Widget._check_anchor() to allow re-engagement based on scroll_target_y as well as scroll_y.
  • Prevent “scroll-to-bottom” operations from releasing the anchor, and ensure anchor state is restored when targeting the bottom.
  • Call _check_anchor() during compositor layout before applying anchored “glue to bottom” behavior; add a streaming regression test.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
tests/test_anchor.py Adds regression coverage for anchor release + re-engagement during incremental mounting.
src/textual/widget.py Updates anchor restoration criteria and modifies scroll methods to preserve anchoring when targeting the bottom.
src/textual/_compositor.py Ensures anchor state is re-checked during layout before applying anchored scroll positioning.
Suppressed comments (1)

src/textual/widget.py:2909

  • Same as _scroll_to: the release_anchor parameter docs here currently imply anchor release happens whenever release_anchor=True, but this method now intentionally preserves anchoring when scrolling to/beyond max_scroll_y. Please update the docstring line so it matches the new semantics.
        if release_anchor:
            if y is not None and y >= self.max_scroll_y:
                release_anchor = False
                if self._anchored:
                    self._anchor_released = False
            else:

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +609 to 612
widget._check_anchor()
if widget._anchored and not widget._anchor_released:
new_scroll_y = (
arrange_result.spatial_map.total_region.bottom
Comment thread src/textual/widget.py
Comment on lines 2753 to +2757
if release_anchor:
self.release_anchor()
if y is not None and y >= self.max_scroll_y:
release_anchor = False
if self._anchored:
self._anchor_released = False
Remove �nd not widget.screen._selecting condition in Visual.to_strips so link color and underline styles remain active when widgets re-render during or after text selection.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scrolling back to the bottom does not restore anchored scroll

2 participants