diff --git a/meshterm/ui/dashboard_screen.py b/meshterm/ui/dashboard_screen.py index 3412b7d..a932c1a 100644 --- a/meshterm/ui/dashboard_screen.py +++ b/meshterm/ui/dashboard_screen.py @@ -53,6 +53,7 @@ from ..core.events import MeshEvent from ..core.models import NODE_TYPE_REPEATER, Observation, utcnow from ..core.regions import Scope from ..persistence.repository import OBSERVATION_WINDOW +from ..platforms import Platform, on_platform from ..services.monitor_service import ACTIVITY_BUCKET_S, ACTIVITY_BUCKETS from .braillechart import axis_chart, axis_chrome, axis_label_w, meter, timeline_rows from .packet_viewer import KIND_STYLES, payload_marks @@ -125,6 +126,24 @@ _GRID_LABEL_W = 9 #: The screen's title, before any scope atom. _TITLE = "Dashboard — mesh overview" +#: What the whole-mesh activity chart counts, as the heading's aside says it. The desktop +#: adds the chart's unit, ``one minute per dot column``; a font that draws braille solid +#: (the PicoCalc's, the Cardputer's — :attr:`~meshterm.platforms.Platform.solid_braille`) +#: has no dots to count, and on 53 columns the words cost the heading its line +#: (JP, 2026-10-04). The axis under the chart still marks the minutes. +_ACTIVITY_ASIDE = "every packet heard · one minute per dot column" + + +@on_platform +def _bind_activity_aside(platform: Platform) -> None: + """Bind the activity heading's aside to the platform's braille (runs now and on switch).""" + global _ACTIVITY_ASIDE + _ACTIVITY_ASIDE = ( + "every packet heard" + if platform.solid_braille + else "every packet heard · one minute per dot column" + ) + class _ScopeDigest(NamedTuple): """One scope view's numbers over the window, in the shapes the sections draw. @@ -429,11 +448,7 @@ class DashboardScreen(Screen): heading = Text("Activity", style="accent") # A narrowed view spends the aside on what it counts, which the reader has to be # told; the minute per column is on the axis beneath either way. - what = ( - "floods in this scope · last 2 h" - if self._scope - else "every packet heard · one minute per dot column" - ) + what = "floods in this scope · last 2 h" if self._scope else _ACTIVITY_ASIDE heading.append(f" · {what}", style="muted") # Buckets seeded from a previous session's stored history draw grey; only # what this session heard itself pulses green. diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index b703a15..52d99c5 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -89,6 +89,24 @@ def test_dashboard_renders_all_three_sections() -> None: assert "advert" in body and "Alice" in body # traffic class + the busiest node +def test_the_activity_aside_names_dot_columns_only_where_braille_has_dots() -> None: + """The handhelds draw braille solid, so the heading drops ``one minute per dot column``. + + There are no dots there to count, and on 53 columns the words cost the heading its + line. The desktop keeps them; both keep what the chart counts. + """ + from meshterm.platforms import PICOCALC, REGULAR, set_platform + + def heading(width: int) -> str: + body = _plain(_screen(histogram=[1] * ACTIVITY_BUCKETS).render_body(width)) + return next(line for line in body.splitlines() if "Activity" in line) + + set_platform(PICOCALC) + assert heading(53).strip() == "Activity · every packet heard" + set_platform(REGULAR) + assert heading(72).strip() == "Activity · every packet heard · one minute per dot column" + + def test_dashboard_activity_chart_reads_newest_right_with_mirrored_scale() -> None: """'now' anchors the right edge and the scale marks mirror on both gutters.