Atlas breadcrumbs: key-coloured, loop-truncated, tail-anchored

Three changes to the walk trail:

- Each name now carries its node's own key-derived hue (ours the white
  you-style, a nameless node muted), the focus bold — instead of a flat
  muted/bold trail.
- Walking to a node already on the trail truncates the stack to that
  node's first appearance, dropping the loop we walked to get back there
  rather than recording the round trip (this also subsumes the old
  step-back-through-the-west-node special case).
- When the trail outgrows the line the head is dropped behind a leading
  "…" rather than the tail being cut, so the focus and the steps that led
  to it stay visible.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jean-Pierre MartineauandClaude Opus 4.8 committed 2026-07-19 16:12:26 -04:00
1 parent 223baee3ec
commit 0f97c0d752
2 files changed
+106 -14

No files matched your search

+52 -14
View File
@@ -28,8 +28,12 @@ only its immediate neighbourhood:
PgUp/PgDn a windowful.
**Enter walks**: the highlighted neighbour becomes the new focus, the breadcrumb trail
across the top grows (``you › YUL-Cartierville › …``), and **⌫ steps back** along it.
**Home** refocuses our own node. **Typing finds** — a global filter over every node in
across the top grows (``you › YUL-Cartierville › …``, each name in its node's own hue),
and **⌫ steps back** along it. Walking to a node already on the trail truncates the stack
to its first appearance — the loop you walked to get back there is dropped rather than
recorded — and when the trail outgrows the line its *head* is dropped behind a leading
``…`` so the focus stays visible. **Home** refocuses our own node. **Typing finds** — a
global filter over every node in
the graph, islands included; Enter teleports the focus to the highlighted match (the
trail restarts there, since the walk didn't cross the gap). ``^R`` rebuilds the graph
from storage, and Esc peels find first, the screen second.
@@ -42,6 +46,7 @@ from collections import deque
from datetime import datetime
from typing import TYPE_CHECKING, Callable, Optional
from rich.cells import cell_len
from rich.text import Text
from ..core.models import (
@@ -337,8 +342,12 @@ class AtlasScreen(Screen):
# gap, so pretending it did would make ⌫ retrace a path never taken.
self._trail = [target]
self._filter = ""
elif target == self._came_from:
self._trail.pop() # walking back through the west node = one step back
elif target in self._trail:
# Revisiting a node already on the trail — stepping back through the west
# node, or looping round to an earlier one — truncates the stack to that
# node's first appearance. We drop the circular stretch we walked to get
# back here rather than recording the round trip; losing the loop is the point.
self._trail = self._trail[: self._trail.index(target) + 1]
else:
self._trail.append(target)
self._index = 0
@@ -425,19 +434,48 @@ class AtlasScreen(Screen):
"""The breadcrumb trail (when walking) and the focus node's identity line."""
out: list[str] = []
if len(self._trail) > 1:
trail = Text()
for i, node in enumerate(self._trail):
if i:
trail.append(" › ", style="muted")
last = i == len(self._trail) - 1
trail.append(
self._label(node), style="bold" if last else "muted"
)
trail.truncate(width, overflow="ellipsis")
out.append(render_to_ansi(trail, width, no_wrap=True))
out.append(render_to_ansi(self._trail_text(width), width, no_wrap=True))
out.append(render_to_ansi(self._focus_line(depths), width, no_wrap=True))
return out
def _trail_text(self, width: int) -> Text:
"""The breadcrumb trail, each name in its own key-derived hue, tail-anchored.
Names carry the app-wide per-node hue (ours the white ``you``, a nameless node
muted), the focus bold. When the whole trail won't fit on the line the *head* is
dropped behind a leading ``…`` — never the tail — so the focus and the steps that
led to it are always the ones kept in view.
"""
nodes = self._trail
sep = " › "
def width_of(start: int) -> int:
total = cell_len("…") + cell_len(sep) if start else 0 # the leading "… › "
for k in range(start, len(nodes)):
total += cell_len(sep) if k > start else 0
total += cell_len(self._label(nodes[k]))
return total
start = 0
while start < len(nodes) - 1 and width_of(start) > width:
start += 1
trail = Text()
if start:
trail.append("…" + sep, style="muted")
for k in range(start, len(nodes)):
if k > start:
trail.append(sep, style="muted")
last = k == len(nodes) - 1
trail.append(self._label(nodes[k]), style=self._trail_style(nodes[k], last))
trail.truncate(width, overflow="ellipsis") # guard a lone label wider than the line
return trail
def _trail_style(self, node: str, last: bool) -> str:
"""A trail name's style: its key hue (ours white, a nameless node muted), focus bold."""
base = self._list_name_style(node)
return f"bold {base}" if last else base
def _focus_line(self, depths: dict[str, int]) -> Text:
"""Who is in focus: glyph, name, hash, kind, distance, and recency."""
node = self._focus
+54
View File
@@ -134,6 +134,60 @@ def test_atlas_walking_into_the_back_node_pops_instead_of_growing() -> None:
assert screen._trail == [topo.self_id] # popped, not [us, yul, us]
def test_atlas_walking_to_an_earlier_node_drops_the_loop() -> None:
"""Revisiting a node already on the trail truncates the stack to its first appearance,
dropping the circular stretch walked to get back there."""
topo = MeshTopology(US, contacts=[YUL, ALICE])
yul = topo.canonical(YUL.public_key)
alice = topo.canonical(ALICE.public_key)
when = utcnow()
topo.add_walk([topo.self_id, yul], snrs=[6.0], when=when, source="trace")
topo.add_walk([yul, alice], snrs=[-2.0], when=when, source="packet")
topo.add_walk([topo.self_id, alice], snrs=[3.0], when=when, source="trace") # closes the loop
screen = _screen(topo)
def walk_to(node: str) -> None:
screen.render_body(80)
screen._index = screen._rows().index(node)
screen.handle("enter")
walk_to(yul) # us → YUL
walk_to(alice) # YUL → Alice
assert screen._trail == [topo.self_id, yul, alice]
walk_to(yul) # Alice → YUL: loops back, so Alice is dropped, not re-appended
assert screen._trail == [topo.self_id, yul]
def test_atlas_trail_drops_the_head_not_the_tail_when_narrow() -> None:
"""A trail too long for the line loses its head behind a leading …, keeping the focus."""
screen = _screen(_topo())
us = screen._topo.self_id
yul = screen._topo.canonical(YUL.public_key)
alice = screen._topo.canonical(ALICE.public_key)
screen._trail = [us, yul, alice]
text = screen._trail_text(20).plain # too narrow for the whole "Homestead › … › Alice"
assert text.startswith("…")
assert text.endswith("Alice") # the focus is always kept
assert "Homestead" not in text # the head was dropped, not the tail
def test_atlas_trail_names_carry_their_node_hues() -> None:
"""Trail names take the per-node key hue (ours the white you-style), the focus bold."""
from meshterm.ui.theme import node_style
screen = _screen(_topo())
us = screen._topo.self_id
yul = screen._topo.canonical(YUL.public_key)
screen._trail = [us, yul]
text = screen._trail_text(80)
plain = text.plain
yul_at = plain.index("YUL-Cartierville")
assert any(
s.start <= yul_at < s.end and "bold" in str(s.style) and node_style(yul) in str(s.style)
for s in text.spans
)
def test_atlas_home_refocuses_us() -> None:
"""Home resets the walk to our own node from anywhere."""
topo = _topo()