From ff5ce6c446f610aad228202415bc7e8e0b9ac9ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Davor=20Raci=C4=87?= Date: Thu, 9 Jul 2026 23:13:16 +0200 Subject: [PATCH 1/2] test(tui): de-flake near-tail journal-jump scroll assertion The jump handler defers its scroll via a set_timer until the RichLog flushes its lines and grows tall enough to scroll. The test waited on a bare `is_vertical_scroll_end`, which is trivially true in the pre-flush empty state (scroll_y == max_scroll_y == 0), so on a fast capture it sampled anchored=0 before the deferred scroll landed at the tail. The delayed jump then moved the view to 188, tripping `assert 188 == 0`. Gate the wait on `max_scroll_y > 0 and is_vertical_scroll_end` so it samples only after the content is actually rendered at the tail - the tail-variant of the `0 < scroll_y < max_scroll_y` guard its sibling jump test already uses. Also add the flaky marker its two sibling journal-jump tests carry for the orthogonal poll-supersede stall. No product change; the anchor logic was already correct. --- tests/test_tui_app.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_tui_app.py b/tests/test_tui_app.py index 193aface..296402d2 100644 --- a/tests/test_tui_app.py +++ b/tests/test_tui_app.py @@ -535,6 +535,7 @@ async def test_journal_jump_pins_other_sessions_log(project): assert "(pinned" not in log_text(screen) +@pytest.mark.flaky(reruns=2, reruns_delay=1) async def test_journal_jump_near_tail_does_not_chase_growing_log(project): # Regression for "pressing enter keeps sending me to the bottom": jumping to # an entry near the end lands the view at the tail, and the old code then @@ -557,7 +558,11 @@ async def test_journal_jump_near_tail_does_not_chase_growing_log(project): journal_list.focus() await pilot.press("end", "enter") # jump to the near-tail checkpoint log = screen.query_one("#log", RichLog) - await until(pilot, lambda: log.is_vertical_scroll_end) # landed at the tail + # Wait for the jump to actually settle at the tail: max_scroll_y > 0 proves + # the RichLog flushed its lines (an empty/unflushed pane is trivially "at + # scroll end" with scroll_y == max == 0, which would sample anchored=0 before + # the deferred _scroll_log_to timer runs, then fail when the jump lands late). + await until(pilot, lambda: log.max_scroll_y > 0 and log.is_vertical_scroll_end) anchored, base_max = log.scroll_y, log.max_scroll_y # the live session keeps writing; a poll repaints the pane with (run_dir / "logs" / "story-1.log").open("ab") as f: From 3bb17c14a44665aa5101848fe2da13802918fe6f Mon Sep 17 00:00:00 2001 From: pbean Date: Thu, 9 Jul 2026 21:21:12 -0700 Subject: [PATCH 2/2] test(tui): drain pending deferred jump before sampling the anchor The flush's own scroll_end can satisfy the settled-at-tail wait while one deferred _scroll_log_to retry is still armed; if that fire landed after the growth-render it would re-scroll against the grown content and re-flake the anchor assert. Pause two timer periods so the chain provably drains at the tail before the anchor is sampled. --- tests/test_tui_app.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_tui_app.py b/tests/test_tui_app.py index 296402d2..1898efe8 100644 --- a/tests/test_tui_app.py +++ b/tests/test_tui_app.py @@ -563,6 +563,12 @@ async def test_journal_jump_near_tail_does_not_chase_growing_log(project): # scroll end" with scroll_y == max == 0, which would sample anchored=0 before # the deferred _scroll_log_to timer runs, then fail when the jump lands late). await until(pilot, lambda: log.max_scroll_y > 0 and log.is_vertical_scroll_end) + # The flush's own scroll_end can satisfy the wait while one deferred + # _scroll_log_to retry is still armed; post-flush that fire clamps to the + # tail and ends the chain, but landing after the growth-render below would + # re-scroll against the grown content. Drain it before sampling the anchor. + await pilot.pause(0.12) + assert log.is_vertical_scroll_end # chain drained at the tail, not mid-log anchored, base_max = log.scroll_y, log.max_scroll_y # the live session keeps writing; a poll repaints the pane with (run_dir / "logs" / "story-1.log").open("ab") as f: