The packet viewer states a flood's scope on a row of its own

The region rode the route row as a trailing atom (flood · scope harbour), easy to miss on a card opened from the feed's SCOPE lane to see it. Route now says how the packet was routed (flood, direct) and a labelled scope row underneath says where: the region, 'unknown region · code 3fa1', or 'unscoped'. A direct packet has no scope row.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jean-Pierre MartineauandClaude Opus 5.5 committed 2026-09-25 00:14:51 -04:00
1 parent 7f59bfa5a0
commit fcd70c2e2e
3 files changed
+61 -42

No files matched your search

+2 -2
View File
@@ -38,8 +38,8 @@ one caveat SemVer makes for a leading zero: while the major version is still `0`
`channels scope INDEX [REGION]` reads or sets it, `channels list` gains a `SCOPE` column,
and `chat send --channel N --scope REGION` (or `--scope '*'` for unscoped) overrides it
for one message. Scoped sending needs firmware 1.10; `*` needs 1.16.
- **Packets say which region a flood was scoped to.** The packet viewer's route row, the
message paths title, and a new `SCOPE` lane in the live feed name the region a scoped
- **Packets say which region a flood was scoped to.** A `scope` row in the packet viewer,
the message paths title, and a new `SCOPE` lane in the live feed name the region a scoped
flood was sent into, or its code when no region known here matches it. `monitor` gains
the same `SCOPE` column and a `scope` field in `--json`. A scoped packet is kept with
what is needed to name its region later, so one heard today is named once a repeater or
+32 -17
View File
@@ -940,7 +940,10 @@ class PacketViewer(Screen):
rows.extend(self._addressing_rows(raw))
route = raw.get("route_typename")
if route:
rows.append(("route", self._route_text(route, self._entry_scope(entry))))
scope = self._entry_scope(entry)
rows.append(("route", self._route_text(route, scope)))
if scope is not None:
rows.append(("scope", self._scope_text(scope)))
rows.append(("via", self._via_path(entry)))
# The figure the chain above encodes but never states, hung under it as the app's
# own hop atom — the same one the node page's routes and the trace scenarios carry
@@ -995,27 +998,39 @@ class PacketViewer(Screen):
@staticmethod
def _route_text(route: str, scope: Scope | None) -> Text:
"""The ``route`` row: how the frame was routed, and for a flood, where to.
"""The ``route`` row: how the frame was routed — ``flood`` or ``direct``.
A flood reads ``flood`` and then its scope as a chained atom
(:func:`~meshterm.ui.widgets.scope_text`) — ``flood · scope harbour``,
``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
is the scope that is spelled out.
A scoped flood reads ``flood``, not the library's ``tc flood``: that named the wire
mechanism (transport codes) and left the reader to know it meant *scoped*. A scoped
and an unscoped flood are one routing; where they differ is the region, and that
has a row of its own underneath (:meth:`_scope_text`).
A direct frame keeps its route word alone. A repeater never region-filters a
direct packet, so it has no scope, and a ``TC_DIRECT`` frame's codes are the
"to nowhere" pair a contact share uses, not a region — a word there would claim a
meaning the frame does not carry.
A direct frame keeps its route word alone. A ``TC_DIRECT`` frame's codes are the
"to nowhere" pair a contact share uses, not a region, so it has no scope row.
"""
if scope is None:
return Text(route.replace("_", " ").lower())
text = Text("flood")
text.append(" · ", style="muted")
text.append_text(scope_text(scope))
return text
return Text("flood")
@staticmethod
def _scope_text(scope: Scope) -> Text:
"""The ``scope`` row: the region a flood was sent into, stated on its own line.
Its own row, labelled, rather than an atom trailing the route: it is what a reader
opened the card from the feed's SCOPE lane to see, and a card has the room to say
it in full where the lane had ten cells. The label says *scope*, so the value is
the region alone (``harbour``, in the ``scope`` style); a region no name known here
reproduces reads ``unknown region · code 3fa1``, the code kept so two packets can be
seen to share it; a plain flood reads ``unscoped``.
"""
if scope.state == "scoped" and scope.region:
return scope_text(scope, bare=True)
if scope.scoped:
text = Text("unknown region", style="muted")
if scope.code:
text.append(f" · code {scope.code}", style="muted")
return text
return Text("unscoped", style="muted")
def _addressing_rows(self, raw: dict) -> list[tuple[str, RenderableType]]:
"""Who a frame was for, who it says it was from, and the token it carries.
+27 -23
View File
@@ -86,44 +86,48 @@ def _col(line: str, needle: str) -> int:
# -- the packet viewer -------------------------------------------------------------------
def _route_row(raw: dict, scope_of=None) -> str: # noqa: ANN001
"""The viewer's ``route`` row for one frame, as plain text."""
def _card_row(raw: dict, label: str, scope_of=None) -> str | None: # noqa: ANN001
"""The value of the viewer's ``label`` row for one frame, as plain text (``None``: absent)."""
entry = PacketEntry(when=utcnow(), kind="packet", path="3d", raw=raw)
body = _stripped(
PacketViewer([entry], 0, resolve=lambda h: "", scope_of=scope_of).render_body(80)
)
return next(line for line in body if line.startswith("route")).rstrip()
row = next((line for line in body if line.split(None, 1)[:1] == [label]), None)
return None if row is None else row.split(None, 1)[1].rstrip()
def _has_scope_row(text: str, value: str) -> bool:
"""Whether a rendered card carries a ``scope`` row reading ``value``."""
return re.search(rf"^scope\s+{re.escape(value)}\s*$", text, re.M) is not None
def test_the_viewer_names_the_region_a_flood_was_scoped_to() -> None:
"""``tc flood`` said *how* and never *where*; the row now says flood, then the scope."""
assert _route_row(_scoped("harbour"), _knows("harbour")).split(None, 1)[1] == (
"flood · scope harbour"
)
"""``tc flood`` said *how* and never *where*: route says flood, its own row the region."""
assert _card_row(_scoped("harbour"), "route", _knows("harbour")) == "flood"
assert _card_row(_scoped("harbour"), "scope", _knows("harbour")) == "harbour"
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 · unknown scope {_code('elsewhere')}"
assert _card_row(_scoped("elsewhere"), "scope", _knows("harbour")) == (
f"unknown region · code {_code('elsewhere')}"
)
assert _route_row(_FLOOD, _knows("harbour")).endswith("flood · unscoped")
assert _card_row(_FLOOD, "scope", _knows("harbour")) == "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 · unknown scope {_code('harbour')}")
assert _route_row(_FLOOD).endswith("flood · unscoped")
assert _card_row(_scoped("harbour"), "scope") == f"unknown region · code {_code('harbour')}"
assert _card_row(_FLOOD, "scope") == "unscoped"
def test_the_viewer_never_gives_a_direct_frame_a_scope() -> None:
"""A repeater never region-filters a direct packet, so its route row is the word alone."""
assert _route_row(_DIRECT, _knows("harbour")).split() == ["route", "direct"]
assert _route_row({**_DIRECT, "route_typename": "TC_DIRECT"}).split() == [
"route",
"tc",
"direct",
]
"""A repeater never region-filters a direct packet: its route word alone, no scope row."""
assert _card_row(_DIRECT, "route", _knows("harbour")) == "direct"
assert _card_row(_DIRECT, "scope", _knows("harbour")) is None
tc_direct = {**_DIRECT, "route_typename": "TC_DIRECT"}
assert _card_row(tc_direct, "route") == "tc direct"
assert _card_row(tc_direct, "scope") is None
def test_the_viewer_keeps_the_scope_body_out_of_the_raw_dump(tmp_path: Path) -> None:
@@ -139,7 +143,7 @@ def test_the_viewer_keeps_the_scope_body_out_of_the_raw_dump(tmp_path: Path) ->
scope_of=_knows("harbour"),
).render_body(80)
)
assert "scope harbour" in body # a replayed frame resolves exactly as a live one
assert _has_scope_row(body, "harbour") # a replayed frame resolves exactly as a live one
assert "scope_body" not in body
@@ -148,9 +152,9 @@ def test_the_viewer_renames_a_scope_the_moment_its_region_is_learned(tmp_path: P
store = RegionStore(tmp_path / "regions.json")
entry = PacketEntry(when=utcnow(), kind="packet", path="", raw=_scoped("harbour"))
viewer = PacketViewer([entry], 0, resolve=lambda h: "", scope_of=store.scope_of)
assert "scope harbour" not in _plain(viewer.render_body(80))
assert not _has_scope_row(_plain(viewer.render_body(80)), "harbour")
store.learn("harbour", "typed")
assert "scope harbour" in _plain(viewer.render_body(80))
assert _has_scope_row(_plain(viewer.render_body(80)), "harbour")
# -- the live feed -----------------------------------------------------------------------
@@ -220,7 +224,7 @@ def test_the_feed_hands_its_scope_reader_to_the_viewer() -> None:
screen._session.run_screen = lambda viewer: opened.append(viewer) # noqa: SLF001
screen._session.run_detached = lambda coro: None # noqa: SLF001
screen.handle("enter")
assert "flood · scope harbour" in _plain(opened[0].render_body(80))
assert _has_scope_row(_plain(opened[0].render_body(80)), "harbour")
# -- message paths -----------------------------------------------------------------------