From acd14b3adf329698ed5eadadb6daa8ff35cf47dd Mon Sep 17 00:00:00 2001 From: Jean-Pierre Martineau Date: Thu, 24 Sep 2026 21:10:30 -0400 Subject: [PATCH] An unnamed scope reads as one fact, not two title atoms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "scoped · 3fa1" put a `·` inside the reading, so in the message paths title it chained as two status atoms. It is now "unknown scope 3fa1", and "? 3fa1" in the live feed's SCOPE lane, whose heading already says what the column holds. Co-Authored-By: Claude Opus 5.5 (1M context) --- meshterm/ui/livefeed_screen.py | 6 +++--- meshterm/ui/message_paths_screen.py | 2 +- meshterm/ui/packet_viewer.py | 4 ++-- meshterm/ui/widgets.py | 9 ++++++--- tests/test_scope_display.py | 10 ++++++---- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/meshterm/ui/livefeed_screen.py b/meshterm/ui/livefeed_screen.py index 48eaefb..7612054 100644 --- a/meshterm/ui/livefeed_screen.py +++ b/meshterm/ui/livefeed_screen.py @@ -12,7 +12,7 @@ graph. The feed's job is to say what arrived and how well it was heard; Enter sa it got here. Where the width allows, a **scope** lane follows the readings: the region a flood was sent -into (``harbour``), ``scoped · 3fa1`` for a region nobody here has named, ``unscoped`` for a +into (``harbour``), ``unknown scope 3fa1`` for a region nobody here has named, ``unscoped`` for a plain flood, and nothing for a direct frame, which no repeater region-filters (see :func:`_lanes_for` for which widths hold it, and why the PicoCalc's never does). @@ -161,7 +161,7 @@ _FEED_LABEL_MIN_WIDTH = ( ) #: The scope lane's width: the longest thing :func:`~meshterm.ui.widgets.scope_text` draws -#: for an unnamed scope (``scoped · 3fa1``), so the one reading that carries a code keeps +#: for an unnamed scope (``unknown scope 3fa1``), so the one reading that carries a code keeps #: it whole. A named region longer than this ellipsizes — its whole name is on the #: viewer's ``route`` row, one keypress away, like everything else a lane cuts. _FEED_SCOPE_WIDTH = 13 @@ -892,7 +892,7 @@ class LiveFeedScreen(Screen): read from, and only a flood has one; the lane is left blank for everything else — a direct frame is never region-filtered, so a word there would claim a meaning it lacks. The lane is drawn ``bare``: its heading already says ``SCOPE``, so a known - region reads as its name alone, while ``unscoped`` and ``scoped · 3fa1`` stay the + region reads as its name alone, while ``unscoped`` and ``unknown scope 3fa1`` stay the words they are everywhere (:func:`~meshterm.ui.widgets.scope_text`). """ if entry.kind != "packet" or not isinstance(entry.raw, dict): diff --git a/meshterm/ui/message_paths_screen.py b/meshterm/ui/message_paths_screen.py index 10ba218..725385b 100644 --- a/meshterm/ui/message_paths_screen.py +++ b/meshterm/ui/message_paths_screen.py @@ -192,7 +192,7 @@ class MessagePathsScreen(Screen): status atoms chain (``Message paths · scope harbour``) and it costs the body no line: on the PicoCalc's 26 rows a line spent here is a lane of the fan or a row of the list. The words are :func:`~meshterm.ui.widgets.scope_text`'s, plain — a title - carries no styling — so ``unscoped`` and ``scoped · 3fa1`` read here exactly as they + carries no styling — so ``unscoped`` and ``unknown scope 3fa1`` read here exactly as they do on the packet viewer's ``route`` row. """ atom = scope_text(scope).plain diff --git a/meshterm/ui/packet_viewer.py b/meshterm/ui/packet_viewer.py index ddb4dda..60c0be5 100644 --- a/meshterm/ui/packet_viewer.py +++ b/meshterm/ui/packet_viewer.py @@ -85,7 +85,7 @@ def unnamed_scope(raw: dict | None) -> Scope | None: """A frame's scope read without any region names — the fallback when none are at hand. Still says everything the frame itself says: a plain flood is ``unscoped``, and a - scoped one is ``scoped · ``. Only the *naming* needs the known regions (see + scoped one is ``unknown scope ``. Only the *naming* needs the known regions (see :meth:`~meshterm.core.region_store.RegionStore.scope_of`), so a viewer opened without a store never draws less than the truth, only less than the whole of it. """ @@ -999,7 +999,7 @@ class PacketViewer(Screen): A flood reads ``flood`` and then its scope as a chained atom (:func:`~meshterm.ui.widgets.scope_text`) — ``flood · scope harbour``, - ``flood · scoped · 3fa1``, ``flood · unscoped`` — rather than the library's + ``flood · unknown scope 3fa1``, ``flood · unscoped`` — rather than the library's ``tc flood``, which named the wire mechanism (transport codes) and left the reader to know that it meant *scoped*, and to which region nobody could say. A scoped and an unscoped flood are one routing; the scope is what differs, so it diff --git a/meshterm/ui/widgets.py b/meshterm/ui/widgets.py index ce8d2cf..d5ec8c8 100644 --- a/meshterm/ui/widgets.py +++ b/meshterm/ui/widgets.py @@ -102,7 +102,8 @@ def scope_text(scope: Scope | None, *, bare: bool = False) -> Text: * ``scope yul`` — a scoped flood whose region is known, the name in the ``scope`` style (a region is not a node, so it never takes a node hue); - * ``scoped · 3fa1`` — a scoped flood no known name reproduces: the code is shown so + * ``unknown scope 3fa1`` (``? 3fa1`` bare) — a scoped flood no known name + reproduces: the code is shown so two frames can still be told to share a region, ``muted`` because it names nothing; * ``unscoped`` — a plain flood, ``muted``: the ordinary case, stated not stressed. @@ -125,9 +126,11 @@ def scope_text(scope: Scope | None, *, bare: bool = False) -> Text: text.append("scope ", style="muted") text.append(scope.region, style="scope") elif scope.scoped: - text.append("scoped", style="muted") + # No `·` inside the reading: a title chains its status atoms with `·`, and + # ``scoped · 3fa1`` there read as two atoms where it is one fact. + text.append("?" if bare else "unknown scope", style="muted") if scope.code: - text.append(f" · {scope.code}", style="muted") + text.append(f" {scope.code}", style="muted") else: text.append("unscoped", style="muted") return text diff --git a/tests/test_scope_display.py b/tests/test_scope_display.py index ff687b8..91d5605 100644 --- a/tests/test_scope_display.py +++ b/tests/test_scope_display.py @@ -105,14 +105,14 @@ def test_the_viewer_names_the_region_a_flood_was_scoped_to() -> None: def test_the_viewer_shows_an_unnamed_scopes_code_and_a_plain_flood_as_unscoped() -> None: """A scope no known name reproduces keeps its code; a plain flood reads unscoped.""" assert _route_row(_scoped("elsewhere"), _knows("harbour")).endswith( - f"flood · scoped · {_code('elsewhere')}" + f"flood · unknown scope {_code('elsewhere')}" ) assert _route_row(_FLOOD, _knows("harbour")).endswith("flood · unscoped") def test_the_viewer_without_a_store_still_tells_scoped_from_unscoped() -> None: """No names at hand is less of the truth, never a wrong one: the code stands in.""" - assert _route_row(_scoped("harbour")).endswith(f"flood · scoped · {_code('harbour')}") + assert _route_row(_scoped("harbour")).endswith(f"flood · unknown scope {_code('harbour')}") assert _route_row(_FLOOD).endswith("flood · unscoped") @@ -182,7 +182,7 @@ def test_the_feed_carries_a_scope_lane_on_a_72_column_terminal() -> None: header, *rows = _stripped(screen.render_body(68)) assert header.split()[-1] == "SCOPE" assert rows[0].rstrip().endswith("harbour") - assert rows[1].rstrip().endswith(f"scoped · {_code('elsewhere')}") + assert rows[1].rstrip().endswith(f"? {_code('elsewhere')}") assert rows[2].rstrip().endswith("unscoped") assert rows[3].rstrip().endswith("dBm") # the direct frame states no scope for line in (header, *rows): @@ -293,7 +293,9 @@ def test_the_paths_dialog_states_the_scope_once_in_its_title() -> None: assert _paths_screen(Scope("scoped", "harbour", "3fa1")).title == ( "Message paths · scope harbour" ) - assert _paths_screen(Scope("unknown", None, "3fa1")).title == "Message paths · scoped · 3fa1" + assert ( + _paths_screen(Scope("unknown", None, "3fa1")).title == "Message paths · unknown scope 3fa1" + ) assert _paths_screen(UNSCOPED).title == "Message paths · unscoped" assert _paths_screen(None).title == "Message paths" # …and never per arrival row: the body does not repeat it.