Time Machine arrivals: the key lane fills the terminal
The mesh page's arrivals showed a fixed 16-cell lane — six stored bytes and blank padding, however wide the terminal. The lane now absorbs whatever width the name lane and the FIRST HEARD tail leave (floored at the old width), and each arrival's key expands to its fullest known form the way the picker's lane does: the key captured with an observation, else the device's contact list, else the stored 12-hex prefix — as many whole bytes as fit, ellipsized on a byte boundary. The column header reads KEY, per the lexicon. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
93c1937de7
commit
6ccdfbddc2
2 files changed
+77
-14
No files matched your search
@@ -808,6 +808,7 @@ def _mesh_sections(
|
||||
width: int,
|
||||
prefix_bytes: int = 0,
|
||||
resolve: "NodeResolver" = lambda label: label,
|
||||
resolve_key: "NodeResolver" = lambda node: node,
|
||||
) -> list[RenderableType]:
|
||||
"""Build the whole-mesh overview: days, rhythm, arrivals, and the all-time ledger.
|
||||
|
||||
@@ -815,9 +816,12 @@ def _mesh_sections(
|
||||
ctx: The shared application context (repository reads only).
|
||||
window: The history window (``None`` = everything ever recorded).
|
||||
width: Render width in columns.
|
||||
prefix_bytes: Path-hash width to light in the arrival hashes (0 = none).
|
||||
prefix_bytes: The hash width to light at the head of each arrival's key (0 = none).
|
||||
resolve: Names a node hash from the device's contacts, for arrivals whose
|
||||
observations never carried a name.
|
||||
resolve_key: Expands a stored 12-hex node id to the full public key when the
|
||||
device holds it as a contact, so the arrivals' key lane can fill whatever
|
||||
width the terminal offers (see :func:`_contact_resolvers`).
|
||||
"""
|
||||
now = utcnow()
|
||||
since = now - window if window is not None else None
|
||||
@@ -898,36 +902,48 @@ def _mesh_sections(
|
||||
else:
|
||||
# Aligned lanes under column labels, the picker's presentation: the name in
|
||||
# the node's hash-derived hue (a nameless arrival's "unknown" stays muted),
|
||||
# the hash lit at the routing width, the age glowing with recency heat.
|
||||
# the key lit at the routing width, the age glowing with recency heat.
|
||||
# The FIRST HEARD header carries what used to be repeated on every row.
|
||||
# A nameless arrival first asks the resolver (the device may know the node
|
||||
# as a contact even though its stored observations never carried a name).
|
||||
# as a contact even though its stored observations never carried a name),
|
||||
# and each key expands to its fullest known form the way the picker's lane
|
||||
# does: the key captured with an observation (shows offline), else the
|
||||
# device's contact list, else the stored 12-hex prefix.
|
||||
stored_keys = {n.node: n.public_key for n in ctx.repo.heard_nodes() if n.node}
|
||||
listed = [
|
||||
(node, _known_name(resolve, node, name), first)
|
||||
(
|
||||
stored_keys.get(node) or resolve_key(node) or node,
|
||||
_known_name(resolve, node, name),
|
||||
first,
|
||||
)
|
||||
for node, name, first in arrivals[:12]
|
||||
]
|
||||
name_w = min(
|
||||
_PICK_NAME_MAX,
|
||||
max([len("unknown"), *(len(n) for _node, n, _f in listed if n)]),
|
||||
)
|
||||
# The key lane absorbs whatever width the name lane and the fixed FIRST
|
||||
# HEARD tail leave, floored at the old fixed lane — as many whole bytes as
|
||||
# fit, ellipsized past that on a byte boundary (highlighted_hash's contract).
|
||||
key_w = max(_PICK_HASH_W, width - name_w - _ARRIVAL_TAIL)
|
||||
out.append(
|
||||
Text(
|
||||
" "
|
||||
+ "NAME".ljust(name_w + 2)
|
||||
+ "HASH".ljust(_PICK_HASH_W + 2)
|
||||
+ "KEY".ljust(key_w + 2)
|
||||
+ "FIRST HEARD",
|
||||
style="muted",
|
||||
)
|
||||
)
|
||||
for node, name, first in listed:
|
||||
for key, name, first in listed:
|
||||
secs = _age_seconds(first)
|
||||
line = Text(" ", no_wrap=True, overflow="ellipsis")
|
||||
line.append(
|
||||
fit_cells(name or "unknown", name_w),
|
||||
style=name_style(name, node) if name else "muted",
|
||||
style=name_style(name, key) if name else "muted",
|
||||
)
|
||||
line.append(" ")
|
||||
line.append_text(highlighted_hash(node, prefix_bytes, width=_PICK_HASH_W))
|
||||
line.append_text(highlighted_hash(key, prefix_bytes, width=key_w))
|
||||
line.append(" ")
|
||||
line.append(_when_label(first))
|
||||
line.append(f" ({format_ago(secs)})", style=_recency_style(secs))
|
||||
@@ -961,11 +977,16 @@ def _mesh_sections(
|
||||
#: :meth:`~meshterm.ui.nodelist.NodeListScreen._lane_widths`).
|
||||
_PICK_NAME_MAX = 18
|
||||
|
||||
#: The mesh page's arrivals hash lane in cells. Heard-node ids are the observations' 12-hex
|
||||
#: key prefixes, so the lane's tail pads blank — the extra room keeps the hash legible, and
|
||||
#: as the arrival row's hash column the padding falls before the FIRST HEARD lane.
|
||||
#: The mesh page's arrivals key lane's *floor* in cells (a very narrow terminal). The lane
|
||||
#: otherwise flexes to fill the width the name lane and the FIRST HEARD tail leave, so a
|
||||
#: resolved full key shows as many whole bytes as fit (see the ``key_w`` math above).
|
||||
_PICK_HASH_W = 16
|
||||
|
||||
#: Everything in an arrival row *besides* the name and key lanes, in cells: the 2-cell
|
||||
#: indent, the two 2-cell lane gaps, the ``Jul 04 18:30`` stamp (12), and the recency
|
||||
#: parenthetical (`` (259w ago)`` at its widest, 12).
|
||||
_ARRIVAL_TAIL = 2 + 2 + 2 + 12 + 12
|
||||
|
||||
|
||||
def _known_name(resolve: "NodeResolver", node: Optional[str], name: Optional[str]) -> Optional[str]:
|
||||
"""The best display name for a heard node, or ``None`` when truly unknown.
|
||||
@@ -1193,7 +1214,7 @@ async def open_timemachine(ctx: "AppContext") -> None:
|
||||
label = "the whole mesh"
|
||||
build = ( # noqa: E731
|
||||
lambda window, width, _pb=prefix_bytes: _mesh_sections(
|
||||
ctx, window, width, _pb, resolve
|
||||
ctx, window, width, _pb, resolve, resolve_key
|
||||
)
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -469,14 +469,56 @@ def test_mesh_page_renders_days_rhythm_arrivals_and_ledger(tmp_path: Path) -> No
|
||||
assert "Packets per day" in body and "Nodes per day" in body
|
||||
assert "Rhythm" in body and "15-min" in body # 15-minute slices, four per hour
|
||||
assert "Arrivals" in body and "Newcomer" in body
|
||||
# Arrivals are aligned lanes: the hash sits in its own column and the old
|
||||
# per-row "first heard" prefix now lives once, in the column header.
|
||||
# Arrivals are aligned lanes: the key sits in its own column (labelled KEY,
|
||||
# per the lexicon) and the old per-row "first heard" prefix now lives once,
|
||||
# in the column header.
|
||||
assert "f7" * 6 in body
|
||||
assert "KEY" in body and "HASH" not in body
|
||||
assert "FIRST HEARD" in body and "first heard" not in body
|
||||
assert "Ledger" in body and "26 observations" in body and "2 nodes" in body
|
||||
repo.close()
|
||||
|
||||
|
||||
def test_mesh_page_arrivals_key_lane_flexes_with_width(tmp_path: Path) -> None:
|
||||
"""The arrivals key lane fills the terminal: a resolvable full key shows more
|
||||
than the stored 12 hex, truncating on a byte boundary with an ellipsis; a
|
||||
narrow terminal floors the lane at its old fixed width."""
|
||||
from meshterm.ui.timemachine_screen import _PICK_HASH_W
|
||||
|
||||
repo = _seeded_repo(tmp_path)
|
||||
ctx = SimpleNamespace(repo=repo)
|
||||
full_key = "f7" * 32
|
||||
|
||||
def resolve_key(node):
|
||||
return full_key if node == "f7" * 6 else node
|
||||
|
||||
wide = _plain(
|
||||
_mesh_sections(ctx, None, 100, 1, lambda n: n, resolve_key), width=100
|
||||
)
|
||||
arrivals_row = next(
|
||||
ln for ln in wide.split("\n") if "Newcomer" in ln
|
||||
)
|
||||
shown = re.search(r"(f7)+…?", arrivals_row).group(0)
|
||||
assert len(shown.rstrip("…")) > 12 # more than the stored 12 hex
|
||||
assert len(shown.rstrip("…")) % 2 == 0 # truncated on a byte boundary
|
||||
assert "…" in shown # a 64-hex key can't fit whole
|
||||
|
||||
narrow = _plain(
|
||||
_mesh_sections(ctx, None, 60, 1, lambda n: n, resolve_key), width=60
|
||||
)
|
||||
narrow_row = next(ln for ln in narrow.split("\n") if "Newcomer" in ln)
|
||||
narrow_shown = re.search(r"(f7)+…?", narrow_row).group(0)
|
||||
assert len(narrow_shown) < len(shown) # the lane tracks the terminal width
|
||||
|
||||
tight = _plain(
|
||||
_mesh_sections(ctx, None, 40, 1, lambda n: n, resolve_key), width=40
|
||||
)
|
||||
tight_row = next(ln for ln in tight.split("\n") if "Newcomer" in ln)
|
||||
tight_shown = re.search(r"(f7)+…?", tight_row).group(0)
|
||||
assert len(tight_shown) <= _PICK_HASH_W # floored at the old fixed lane
|
||||
repo.close()
|
||||
|
||||
|
||||
def test_mesh_page_day_chart_axis_matches_the_bar_width(tmp_path: Path) -> None:
|
||||
"""A day chart's bottom border spans exactly as many columns as its bars.
|
||||
|
||||
|
||||
Reference in new issue
Block a user