Packet viewer paging walks the feed highlight in step

The viewer reports each navigation through on_navigate and the
dashboard follows by re-highlighting that packet in the feed, so
closing the viewer lands where the reading left off. Home/End move
the highlight to the newest/oldest packet while it is active (plain
scroll-to-edge otherwise).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jean-Pierre MartineauandClaude Fable 5 committed 2026-07-11 18:44:39 -04:00
1 parent 21c5017706
commit 1205ef211b
2 files changed
+37 -7

No files matched your search

+30 -6
View File
@@ -228,9 +228,17 @@ class DashboardScreen(Screen):
elif action in ("pagedown", "space"):
self.scroll_pages(1)
elif action in ("home", "ctrl_home"):
self.scroll_to_top()
# With the feed highlight active, Home jumps to the newest packet;
# otherwise it keeps its plain scroll-to-top meaning (End mirrors it).
if self._selected is not None:
self._select_index(0)
else:
self.scroll_to_top()
elif action in ("end", "ctrl_end"):
self.scroll_to_bottom()
if self._selected is not None:
self._select_index(len(self._feed) - 1)
else:
self.scroll_to_bottom()
elif action == "escape":
if self._selected is not None:
self._selected = None # first Esc peels the highlight, second leaves
@@ -243,19 +251,35 @@ class DashboardScreen(Screen):
if not self._feed:
return
if self._selected is None:
self._selected = 0
self._select_index(0)
else:
self._selected = max(0, min(self._selected + delta, len(self._feed) - 1))
self._select_index(self._selected + delta)
def _select_index(self, index: int) -> None:
"""Highlight one feed row (clamped) and repaint."""
self._selected = max(0, min(index, len(self._feed) - 1))
self._session.invalidate()
def _open_packet(self) -> None:
"""Float the packet viewer over the highlighted feed row."""
"""Float the packet viewer over the highlighted feed row.
Paging inside the viewer walks the feed highlight in step (via
``on_navigate``), so closing it lands back on the packet last viewed.
"""
if self._selected is None or not self._feed:
return
def follow(entry: PacketEntry) -> None:
# The feed may have grown since the snapshot; find the entry itself.
for i, candidate in enumerate(self._feed):
if candidate is entry:
self._select_index(i)
return
viewer = PacketViewer(
list(self._feed), self._selected,
resolve=self._resolve, prefix_bytes=self._prefix_bytes,
self_name=self._self_name,
self_name=self._self_name, on_navigate=follow,
)
asyncio.ensure_future(self._session.run_screen(viewer))
+7 -1
View File
@@ -20,7 +20,7 @@ from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
from typing import Callable, Optional
from rich.console import RenderableType
from rich.table import Table
@@ -170,6 +170,7 @@ class PacketViewer(Screen):
resolve: NodeResolver,
prefix_bytes: int = 0,
self_name: Optional[str] = None,
on_navigate: Optional[Callable[[PacketEntry], None]] = None,
) -> None:
"""Open the viewer over a packet list.
@@ -180,6 +181,8 @@ class PacketViewer(Screen):
resolve: Maps a node hash to a friendly name when known.
prefix_bytes: Path-hash width to light in displayed hashes (0 = none).
self_name: Our own node's name, drawn white wherever it appears.
on_navigate: Called with the newly shown entry whenever paging moves the
view, so the opening list can walk its own highlight in step.
"""
super().__init__()
self._entries = entries
@@ -187,6 +190,7 @@ class PacketViewer(Screen):
self._resolve = resolve
self._prefix_bytes = prefix_bytes
self._self_name = self_name
self._on_navigate = on_navigate
if len(entries) > 1:
self.footer_hint = "PgUp/PgDn newer/older · Home/End ends · Esc close"
else:
@@ -226,6 +230,8 @@ class PacketViewer(Screen):
self._index = index
self.scroll = 0
self._set_title()
if self._on_navigate is not None:
self._on_navigate(self._entries[index])
# --- rendering -------------------------------------------------------------