From 8daf3f1294039629b934c63c2a2e2a19697a4a1d Mon Sep 17 00:00:00 2001 From: Jean-Pierre Martineau Date: Sun, 19 Jul 2026 14:12:51 -0400 Subject: [PATCH] Dashboard: advertise scroll keys only when the overview overflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The footer named "↑↓ PgUp/PgDn scroll" unconditionally, even on a tall terminal where the whole overview fits and those keys do nothing. Gate the scroll atom on the body actually being taller than its viewport, so Esc is the only key advertised when there is nothing to scroll. Add a reusable Screen.content_overflows predicate (from the metrics the frame already records each paint) for the pattern, so any scrolling screen can gate its footer the same way. Co-Authored-By: Claude Opus 4.8 --- meshterm/ui/dashboard_screen.py | 15 ++++++++++++++- meshterm/ui/tui/screen.py | 12 ++++++++++++ tests/test_dashboard.py | 11 +++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/meshterm/ui/dashboard_screen.py b/meshterm/ui/dashboard_screen.py index f5ade4f..03f3bf4 100644 --- a/meshterm/ui/dashboard_screen.py +++ b/meshterm/ui/dashboard_screen.py @@ -121,7 +121,20 @@ class DashboardScreen(Screen): """The live mesh overview. Renders state and scrolls; the opener feeds it.""" floating = False - footer_hint = "↑↓ PgUp/PgDn scroll · Esc back" + + @property + def footer_hint(self) -> str: # type: ignore[override] + """The footer keys — advertising scroll only when the overview actually overflows. + + The dashboard is one scrolling body, but on a tall terminal it often fits whole; a + static ``↑↓ PgUp/PgDn scroll`` then names keys that do nothing. Show that atom only + while the content is taller than the viewport (see + :attr:`~meshterm.ui.tui.screen.Screen.content_overflows`); otherwise Esc is the only + key that acts. + """ + if self.content_overflows: + return "↑↓ PgUp/PgDn scroll · Esc back" + return "Esc back" def __init__( self, diff --git a/meshterm/ui/tui/screen.py b/meshterm/ui/tui/screen.py index 183816d..bb3782e 100644 --- a/meshterm/ui/tui/screen.py +++ b/meshterm/ui/tui/screen.py @@ -208,6 +208,18 @@ class Screen: """Lines a PageUp/PageDown moves: a screenful, bar one row kept for continuity.""" return max(1, self._scroll_viewport - 1) + @property + def content_overflows(self) -> bool: + """Whether the last-rendered body is taller than its viewport — i.e. it can scroll. + + The reusable gate for the app-wide rule that a footer only advertises a key that + would do something: a screen whose body fits has nothing to scroll, so it drops its + ``↑↓ PgUp/PgDn scroll`` atom (see :attr:`DashboardScreen.footer_hint`). Reads the + metrics the frame recorded on the *previous* paint (:meth:`note_metrics`), so it is + honest one frame after a resize — the frame repaints again immediately. + """ + return self._scroll_total > self._scroll_viewport + def scroll_by(self, delta: int, total: int, viewport: int) -> None: """Adjust :attr:`scroll` by ``delta`` lines, clamped to the given content bounds.""" self.scroll = _clamp_scroll(self.scroll + delta, total, viewport) diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index dd84b23..64c9efa 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -58,6 +58,17 @@ def _stripped(lines: list[str]) -> list[str]: # --- the screen ---------------------------------------------------------------------- +def test_footer_advertises_scroll_only_when_the_overview_overflows() -> None: + """The scroll keys join the footer only when the body is taller than the viewport.""" + screen = _screen() + # Fits (the frame recorded a body no taller than the viewport): Esc alone acts. + screen.note_metrics(total=10, viewport=20) + assert screen.footer_hint == "Esc back" + # Overflows: the scroll atom appears, ahead of Esc. + screen.note_metrics(total=40, viewport=20) + assert screen.footer_hint == "↑↓ PgUp/PgDn scroll · Esc back" + + def test_dashboard_renders_all_three_sections() -> None: """Activity, Traffic, and RF health all render from a seeded window.