diff --git a/meshterm/ui/dashboard_screen.py b/meshterm/ui/dashboard_screen.py index c788715..a0c6421 100644 --- a/meshterm/ui/dashboard_screen.py +++ b/meshterm/ui/dashboard_screen.py @@ -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)) diff --git a/meshterm/ui/packet_viewer.py b/meshterm/ui/packet_viewer.py index 26d70a1..68971ed 100644 --- a/meshterm/ui/packet_viewer.py +++ b/meshterm/ui/packet_viewer.py @@ -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 -------------------------------------------------------------