The graph holds still while the arrivals scroll past it

Message paths laid its whole body out as one strip — quote, fan, captions,
then every arrival — and let the frame scroll it under the selection. Walk
far enough down a busy message and the picture the routes are being compared
against slides off the top of the dialog, which is the one thing the reader
was looking at (JP, 2026-09-04).

The head is pinned now and only the list moves, the app-wide windowed-list
pattern the node detail's Routes tab already runs: the quote and the fan take
their lines, the arrivals window into what is left with faint `↑ n more` /
`↓ n more` edge markers, and the fan compresses its own lane pitch before the
list would lose its window — floored at two arrivals and a marker, because
comparing one arrival against another is what this dialog is for. PgUp/PgDn
now page the selection by a windowful of arrivals rather than by body lines,
and both they and the F-lane's chips say so only while there is more than one
arrival to move between.

The variable-height fit both screens need moves to `ListWindow.fit_blocks`,
beside the single-line `fit` it is the sibling of; node detail drops its own
copy and its hand-kept top/page state for the shared window.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jean-Pierre MartineauandClaude Opus 5 committed 2026-09-04 02:03:25 -04:00
1 parent 6fc4e7afc9
commit 0eb9845cd9
5 files changed
+237 -88

No files matched your search

+57 -2
View File
@@ -30,7 +30,9 @@ def _screen(arrivals: list[Arrival], **kwargs) -> MessagePathsScreen:
summary="heard twice", source="Alice",
)
defaults.update(kwargs)
return MessagePathsScreen(message, arrivals, **defaults)
screen = MessagePathsScreen(message, arrivals, **defaults)
screen.note_viewport(40) # the frame records this before every real paint
return screen
def _arrivals() -> list[Arrival]:
@@ -144,7 +146,7 @@ def test_paths_screen_graph_geometry_holds_still_across_the_selection() -> None:
screen = _screen(arrivals)
shapes = []
for _ in range(len(arrivals)):
shapes.append(_plain(screen._graph_lines(76)))
shapes.append(_plain(screen._graph_lines(76, 15)))
screen.handle("down")
assert len(set(shapes)) == 1, "the graph's shape moved when the selection did"
assert shapes[0].count("\n") > 1 # a genuine multi-lane fan, not one flat line
@@ -329,3 +331,56 @@ def test_a_routed_frame_with_no_path_draws_no_graph_and_says_why() -> None:
body = _plain(screen.render_body(72))
assert "route not carried" in body
assert "origin →" not in body, "no graph, so nothing for the caption to label"
def _many(n: int) -> list[Arrival]:
"""``n`` arrivals, each on its own two-hop route, oldest first."""
now = utcnow()
return [
Arrival(when=now + timedelta(seconds=i), hops=(f"{i:02x}{i:02x}", "77aa"), snr=1.0)
for i in range(n)
]
def test_the_arrival_list_windows_and_the_graph_stays_put() -> None:
"""Only the arrivals scroll: the quote, the fan and its captions are pinned chrome.
Walking to the last arrival slides the window under them — the body still fits the
dialog's budget, and the picture the routes are being compared against never leaves it.
"""
screen = _screen(_many(12))
screen.note_viewport(24)
lines = screen.render_body(72)
assert len(lines) <= 24 # nothing scrolls off: the list took what the head left
body = _plain(lines)
assert "“on my way”" in body and "white = selected path" in body
assert "↓" in body and "more" in body # the edge marker counts the hidden arrivals
assert "PgUp/PgDn scroll" in screen.footer_hint # paging advertised only when needed
for _ in range(11):
screen.handle("down")
lines = screen.render_body(72)
body = _plain(lines)
assert len(lines) <= 24
assert "white = selected path" in body, "the graph is pinned, not scrolled away"
assert "↑" in body and "more" in body # rows now hidden above instead
assert screen.cursor_line() is not None
def test_paging_moves_the_selection_by_a_windowful() -> None:
"""PgDn steps the pick by the rows the window actually carried, not by body lines."""
screen = _screen(_many(12))
screen.note_viewport(24)
screen.render_body(72)
page = screen._list.page
assert 1 <= page < 12
screen.handle("pagedown")
assert screen._index == page
def test_a_short_list_advertises_no_paging() -> None:
"""Every arrival on screen means the pager moves nothing, and the hint says nothing."""
screen = _screen(_arrivals())
screen.render_body(72)
assert "PgUp/PgDn" not in screen.footer_hint
assert "more" not in _plain(screen.render_body(72))
+5 -4
View File
@@ -37,7 +37,7 @@ from meshterm.ui.node_detail_screen import (
)
from meshterm.ui.pathgraph import DST_NODE, SRC_NODE
from meshterm.ui.pathline import CRACK_HEAD, CRACK_TAIL, SELF_GLYPH, PathHop, PathLine
from meshterm.ui.tui.screen import CANCEL
from meshterm.ui.tui.screen import CANCEL, ListWindow
from meshterm.ui.widgets import highlighted_hash, route_graph_style, tab_strip
US = "aaaaaaaaaaaa"
@@ -1009,10 +1009,11 @@ def test_node_detail_route_list_windows_inside_the_page() -> None:
def test_fit_blocks_walks_wrapped_rows_into_view() -> None:
"""The variable-height fit keeps whole blocks, spends marker lines only when rows hide,
and walks the window down to the cursor's row."""
screen = _screen()
top, count = screen._fit_blocks([2, 2, 2, 2], 5, 3) # cursor on the last 2-line row
window = ListWindow()
top, count = window.fit_blocks([2, 2, 2, 2], 5, 3) # cursor on the last 2-line row
assert top + count == 4 and top == 2 # slid to the tail; the last two rows fit
top, count = screen._fit_blocks([1, 1], 5, 0)
assert window.page == count # the settled capacity is the paging stride
top, count = window.fit_blocks([1, 1], 5, 0)
assert (top, count) == (0, 2) # everything fits: no window, no markers