I'm using Textual 8.2.8. An anchored container (widget.anchor()) stops following new content after any scroll_to/scroll_relative/scroll_by call (or scrollbar drag). The only way to restore the anchor is landing exactly on the bottom edge, and since that edge keeps moving while content streams, the anchor effectively never re-engages.
Minimal repro
from textual.app import App, ComposeResult
from textual.containers import VerticalScroll
from textual.widgets import Static
class AnchorStuck(App):
def compose(self) -> ComposeResult:
with VerticalScroll(id="v"):
for i in range(30):
yield Static(f"line {i}")
def on_mount(self) -> None:
v = self.query_one("#v", VerticalScroll)
v.anchor()
self.set_interval(0.3, self.step)
self._steps = 0
def step(self) -> None:
self._steps += 1
v = self.query_one("#v", VerticalScroll)
if self._steps == 1:
v.scroll_relative(y=-1, animate=False) # e.g. a wheel tick while streaming
else:
v.mount(Static(f"line {30 + self._steps}"))
self.log(f"scroll_y={v.scroll_y} max={v.max_scroll_y}")
if __name__ == "__main__":
AnchorStuck().run()
Expected:
while anchored, content growth keeps the viewport glued to the bottom until the user scrolls away.
Actual:
after the scroll_relative, the anchor is released; content keeps growing below the viewport, scroll_y stays frozen, and the new lines are off-screen.
Root cause
scroll_to/scroll_relative/scroll_by default to release_anchor=True (widget.py).
- The compositor bottom-gluing branch requires
_anchored and not _anchor_released (_compositor.py), so it is skipped whenever the anchor was released.
- The only restore path is
_check_anchor in watch_scroll_y, which requires scroll_y >= max_scroll_y (widget.py), practically unreachable while content keeps growing.
Related: #6652 (same compositor anchor branch, negative offset when content is shorter than the viewport).
I'm using Textual 8.2.8. An anchored container (
widget.anchor()) stops following new content after anyscroll_to/scroll_relative/scroll_bycall (or scrollbar drag). The only way to restore the anchor is landing exactly on the bottom edge, and since that edge keeps moving while content streams, the anchor effectively never re-engages.Minimal repro
Expected:
while anchored, content growth keeps the viewport glued to the bottom until the user scrolls away.
Actual:
after the
scroll_relative, the anchor is released; content keeps growing below the viewport,scroll_ystays frozen, and the new lines are off-screen.Root cause
scroll_to/scroll_relative/scroll_bydefault torelease_anchor=True(widget.py)._anchoredandnot _anchor_released(_compositor.py), so it is skipped whenever the anchor was released._check_anchorinwatch_scroll_y, which requiresscroll_y >= max_scroll_y(widget.py), practically unreachable while content keeps growing.Related: #6652 (same compositor anchor branch, negative offset when content is shorter than the viewport).