A per-hop median's path squares only the route's own ends
Each hop row drew its link as a whole route, square at both ends, so every hop read as a walk that began and ended there. A hop is one link of the walk: only the first hop's start and the last hop's finish are the route's own ends. Every other end is a node the route runs on through, and now wears the notch or the point (a bare → in arrow mode), the marks a path line already uses for "this goes on". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ad036a89aa
commit
ff5315ffcf
2 files changed
+48
-10
No files matched your search
@@ -1203,7 +1203,8 @@ class TraceScreen(Screen):
|
||||
Each hop reads ``n origin → destination +4.5 dB meter``, the link drawn through
|
||||
THE path widget as the route lane above draws it: names, no hash after them (an
|
||||
unnamed node shows its hash, the only identity it has), our own end the bare
|
||||
``★``. Where every row fits whole the hops are a four-column table, one line each.
|
||||
``★``, and only the route's own two ends square (see :meth:`_hop_path`). Where
|
||||
every row fits whole the hops are a four-column table, one line each.
|
||||
Where any doesn't, every row takes two — all alike, so the column of readings
|
||||
never jumps between lines as the eye runs down it: the hop number and its path on
|
||||
the first, the reading and its meter flush right on the second. A path still
|
||||
@@ -1214,7 +1215,11 @@ class TraceScreen(Screen):
|
||||
the rows are drawn at the shift the footer and the next press agree on.
|
||||
"""
|
||||
numbers = [Text(str(agg.index), style="muted") for agg in stats.hop_snrs]
|
||||
paths = [self._hop_path(agg, hash_bytes) for agg in stats.hop_snrs]
|
||||
last = len(stats.hop_snrs) - 1
|
||||
paths = [
|
||||
self._hop_path(agg, hash_bytes, from_origin=i == 0, to_destination=i == last)
|
||||
for i, agg in enumerate(stats.hop_snrs)
|
||||
]
|
||||
readings = [
|
||||
Text(f"{agg.median_snr:+.1f} dB", style=snr_style(agg.median_snr))
|
||||
for agg in stats.hop_snrs
|
||||
@@ -1259,8 +1264,20 @@ class TraceScreen(Screen):
|
||||
lines.append(render_to_ansi(second, width, no_wrap=True))
|
||||
return lines
|
||||
|
||||
def _hop_path(self, agg: HopAggregate, hash_bytes: int | None) -> Text:
|
||||
"""One hop's link, ``origin → destination``, as the route lane draws its nodes."""
|
||||
def _hop_path(
|
||||
self,
|
||||
agg: HopAggregate,
|
||||
hash_bytes: int | None,
|
||||
*,
|
||||
from_origin: bool,
|
||||
to_destination: bool,
|
||||
) -> Text:
|
||||
"""One hop's link, ``origin → destination``, as the route lane draws its nodes.
|
||||
|
||||
A hop is one link of the walk, so only the route's own two ends are drawn square —
|
||||
the first hop's start, the last hop's finish. Every other end is a node the route
|
||||
runs on through, and wears the chevron (the arrow form, a bare ``→``) that says so.
|
||||
"""
|
||||
ends = [
|
||||
None if not node or node == self._device_label else node
|
||||
for node in (agg.origin, agg.destination)
|
||||
@@ -1272,6 +1289,8 @@ class TraceScreen(Screen):
|
||||
self_name=self._device_label,
|
||||
hash_bytes=hash_bytes,
|
||||
bare_self=True,
|
||||
from_origin=from_origin,
|
||||
to_destination=to_destination,
|
||||
).text()
|
||||
|
||||
def _trace_log(self) -> RenderableType:
|
||||
|
||||
@@ -389,14 +389,31 @@ def test_hop_rows_name_their_link_on_one_line_where_it_fits() -> None:
|
||||
"""Each hop is ``n origin → destination reading meter`` — names, never a hash."""
|
||||
rows = _hop_rows(_hop_rows_screen(), 100)
|
||||
assert len(rows) == 3 # one line a hop
|
||||
assert rows[1].startswith("1 Lakeside → Mont-Royal Summit Relay")
|
||||
assert "-3.5 dB" in rows[1] and _BAR_FULL in rows[1]
|
||||
assert rows[0].startswith("0 ★ → Lakeside") # our end is the bare star
|
||||
assert not any("(3d" in row or "(f2" in row for row in rows)
|
||||
# Only the route's own ends are bare; every end it runs on through keeps its arrow.
|
||||
assert rows[0].startswith("0 ★ → Lakeside → ") # our end is the bare star
|
||||
assert rows[1].startswith("1 → Lakeside → Mont-Royal Summit Relay → ")
|
||||
assert rows[2].startswith("2 → Mont-Royal Summit Relay → ★ ")
|
||||
# A four-column table: every reading ends in the same column.
|
||||
assert len({row.index(" dB") for row in rows}) == 1
|
||||
|
||||
|
||||
def test_hop_rows_square_only_the_routes_own_ends(powerline) -> None: # noqa: ANN001
|
||||
"""As chips, the first hop opens square and the last closes square — nothing else.
|
||||
|
||||
A hop is one link of the walk, so its far ends are nodes the route runs on through:
|
||||
those wear the notch and the point, the marks a path line uses for "this goes on".
|
||||
"""
|
||||
from meshterm.ui.pathline import POWERLINE_SEP
|
||||
|
||||
powerline(True)
|
||||
rows = _hop_rows(_hop_rows_screen(), 50) # chips are wider: every hop folds
|
||||
paths = [row[2:].rstrip() for row in rows[::2]]
|
||||
assert [p.startswith(POWERLINE_SEP) for p in paths] == [False, True, True]
|
||||
assert [p.endswith(POWERLINE_SEP) for p in paths] == [True, True, False]
|
||||
|
||||
|
||||
def test_hop_rows_fold_under_their_path_and_slide_where_it_does_not() -> None:
|
||||
"""Too narrow for a row, every hop takes two lines; ←→ slide the paths alone.
|
||||
|
||||
@@ -407,7 +424,7 @@ def test_hop_rows_fold_under_their_path_and_slide_where_it_does_not() -> None:
|
||||
screen = _hop_rows_screen()
|
||||
rows = _hop_rows(screen, 30)
|
||||
assert len(rows) == 6 # two lines a hop, all alike
|
||||
assert rows[2].startswith("1 Lakeside → Mont-Royal Su")
|
||||
assert rows[2].startswith("1 → Lakeside → Mont-Royal")
|
||||
assert rows[2].rstrip().endswith("…") # cut where it runs on
|
||||
assert "-3.5 dB" in rows[3] and cell_len(rows[3].rstrip()) == 30 # flush right
|
||||
assert "←→ scroll" in screen.footer_hint
|
||||
@@ -415,13 +432,15 @@ def test_hop_rows_fold_under_their_path_and_slide_where_it_does_not() -> None:
|
||||
screen.handle("down") # the action cursor pins the page…
|
||||
screen.handle("right")
|
||||
assert screen.cursor_line() is None # …and sliding the paths lets it go again
|
||||
screen.handle("right")
|
||||
rows = _hop_rows(screen, 30)
|
||||
assert rows[2].startswith("1 …") and rows[2].rstrip().endswith("Summit Relay")
|
||||
assert rows[0].startswith("0 ★ → Lakeside") # a path in view whole never moves
|
||||
assert rows[2].startswith("1 …") and rows[2].rstrip().endswith("Summit Relay →")
|
||||
assert rows[0].startswith("0 ★ → Lakeside →") # a path in view whole never moves
|
||||
screen.handle("right") # already at the tail: clamped
|
||||
assert _hop_rows(screen, 30)[2] == rows[2]
|
||||
screen.handle("left")
|
||||
assert _hop_rows(screen, 30)[2].startswith("1 Lakeside")
|
||||
screen.handle("left")
|
||||
assert _hop_rows(screen, 30)[2].startswith("1 → Lakeside")
|
||||
|
||||
_hop_rows(screen, 100) # wide enough again: nothing to slide, nothing advertised
|
||||
assert "←→ scroll" not in screen.footer_hint
|
||||
|
||||
Reference in new issue
Block a user