The title is the screen's name now, with no status atoms at all. Everything it
used to carry is already on the screen below it: the breadcrumb trail ends on
the focus, the line under that names the node in colour with its hash and its
ring, and how big the neighbourhood is, is what the canvas and the link list are
for. Set once at construction rather than composed per render, so the top row
holds still while the walk moves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"Mesh walk — YUL-Cartierville · 42 nodes · 61 links · 3 matches" ran 62 cells,
which a 53-column title bar clips rather than wraps — the link count went, and
so did the bar's clip arrows and its Esc hint. The focus was the longest atom,
the one that rewrote the top row at every step of the walk, and the only
redundant one: the breadcrumb trail ends on it and the line under that names it
in colour, with its hash, its ring and when it was last heard.
So the title reads like the map's — the other spatial screen, whose centre is
likewise the body's to name — status atoms only, and the match count swaps for
the node count instead of joining it, the same way the map's does:
Mesh walk · 42 nodes · 61 links
Mesh walk · 3 of 42 match · 61 links
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two fixes to what a path line shows at its edges and at its ends.
The edges: the crack only reached the two surfaces that drew their own marks.
Everywhere else a path still met render_to_ansi(no_wrap=True) or a plain
truncate, both of which ellipsize — so the Message paths rows you were not
on trailed off in dots while the one you were on cracked. Those cuts now go
through cut_to, which reads the line and decides: a chip breaks off on the
crack, prose keeps its ellipsis. That covers the unselected arrival rows,
every select-list row (the trophy walks and probe candidates), the TX sweep
action rows, and the composer's suggestion rows. The generic surfaces are
safe to convert precisely because the fallback is the old behaviour.
The ends: a path's ends are the nodes it went between, so a line that opens
on its first *relay* is naming a node that only passed the message on. The
Message paths rows now run origin → relays → us — the same two endpoints the
graph one row up already drew between, so the row and the picture finally
cross-read. A hopless arrival stops collapsing to the word "direct":
Alice ▶ ★ is what direct delivery looks like, on the same rails as every
relayed row.
And our own end is the ★ everywhere, not our name — it was already so on the
trace lane, the trophy card and the composer, and now also on the node
detail Routes rows, the TX sweep route, the mesh walk trail and the live
feed's subject lane. It is the one node the reader never has to be told, and
in an 18-cell feed lane or a width-starved trail the cells it gives back are
hops that stay in view.
Left alone deliberately: the packet viewer's `via` row (the dialog has a
`from` row naming the origin, so `via` really is just the relay chain), the
verbatim wire spec, and widgets._link_text — a table of FROM → TO *links*,
still on the legacy path_text builder.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The desync JP saw. Hiding the came-from node fixed _rows, and the canvas
reads _rows -- but the list did not. It rendered straight out of the link
table while the cursor indexed the rows, so from the came-from's position
onward every row named one link and drew another: the highlight sat one link
away from the one the canvas was lighting, and the came-from was still
sitting there in the list besides. Rows are now the authority on which links
the list holds and in what order; the table is only ever looked up through
them. Onward counts still gather over the whole table -- that memo is per
focus, not per paint, and a narrowed view must not be what fills it.
Sizing the whole fan to its longest name was the wrong correction. One
29-cell repeater dragged every marker west with it. The fan reaches for a
tip sized to an ordinary name now, and only a marker whose own name wants
more steps west from there, by exactly the difference -- so a long name
costs its own row some easting and leaves the rest of the fan where it was.
And the collapsed slot is laid out for the label it wears at rest, never for
the selection: a stand-in's name would otherwise shuffle the whole fan as
the cursor walked the collapsed nodes. A name too long for the room that
leaves simply truncates.
The stand-in's name goes back to the selection white. Colouring it by
identity was mine to get wrong: a collapsed node is only ever drawn while
selected, so white is not a highlight painted over its colour -- it is
simply what that label is, exactly as it would be with a marker of its own.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The margin the fan kept clear of the east edge was a constant, and the
constant was sized for a name of twenty cells. Anything longer arrived at
the tightest (due-east) marker already ellipsized -- so a repeater called
Sensor-Rooftop-Longueuil-East read as Sensor-Rooftop-Longu... on a canvas
with room to spare, because nothing had asked how wide the names were. The
widest label about to be drawn now sets the margin and the fan pulls west by
exactly that much: short names spread to the edge, a long one buys its
spelling with reach. The floor is a third of the width -- past that the
markers pile onto the focus and the graph stops reading as a fan at all, so
a canvas too narrow for both goes back to clipping. The fan's label cap goes
to 32, the protocol's own limit on a name: a name that fits the mesh now
fits the graph.
And the collapsed marker's rank moves west of the mark. Trailing the name it
read as part of what the node is called and left that one name ending in a
different place from every other name on the fan; ahead of the glyph it is
plainly an annotation on the marker: (2/6) * Dave.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Marks. The canvas coloured every marker by the node's key hue and left the
glyph shape to carry the type alone. That put this one screen at odds with
every other place a typed node is pinned -- the map, the route graph, a
contact row -- where the mark's colour *is* the type and is picked
deliberately per platform through the theme's type.* (a naive downsample
greys the repeater's violet). Identity had its own lane here all along: the
name beside the marker. Shape and colour now agree, room and sensor marks
arrive with them, and the legend keys both because it reads its pairs from
the same places the canvas does.
The way back. The node the walk came in from was drawn ducking in west of
the focus and listed as a link with a "back" tag. It is not a way onward:
walking it just undid the last step, and it took the list's top row, which
is where the cursor lands on every reset -- so the cursor opened on the
node you had that moment left rather than on the strongest link out. It is
gone from both; backspace was always the way back. _fan_nodes is now the
single definition of what the east of this screen is, and the list and the
canvas both read it, find included.
Spacing. The fan spread by angle, so its rows fell out of a sine and
crowded the rim: five neighbours read as a clump. It is a row grid now --
one marker per row, a blank row between each, a blank row above and below
-- with only the easting still an arc, so the fan bows without standing as
a flat column. The outer rows are what gives when the canvas is short: air
is worth a row until it costs a node. The focus sits level with the block's
middle rather than the canvas's, so its edges leave symmetrically.
And the collapsed marker stands in for a selected weak link now, instead of
whiting out an ellipsis: that node's own mark, its own name in its own hue,
and a grey (2/17) for where it sits among the collapsed -- which is also
what keeps the count the old "+n weaker" label carried from being lost the
moment you selected into it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Map. Enter framed the matches, which is only ever half of what finishing a
find means. Typing a query dims everything it doesn't match, and the common
finish is "yes, that one -- now let me look around it": the query has done
its job and only the dimming is in the way. So Enter drops the query and
leaves the view where the reader put it, and ^Enter is the other finish --
take me to them -- keeping the query, because a frame is a place to arrive
and framing tighter from there is one more press.
^Enter is not a keycode any terminal has to offer: Enter *is* ^M, so a
console without modifyOtherKeys has nothing left to modify. What most do
send is LF, which prompt_toolkit spells ControlJ and nothing here was using,
so it is bound; the right-Ctrl rescue covers the rest; and where a console
spells neither, the F3 Frame chip is the affordance -- which is the platform
where that was already the whole story.
Mesh walk. The breadcrumb elided its head and snapped right, so a long walk
had a beginning that could not be read at all. Left and right now scroll it
a hop at a time, and a scrolled line grows its own trailing elision: the
mark means the same thing on either side, more walk that way. It stops where
the head comes into view rather than running the line off the edge, and any
change to the trail rewinds it -- the focus is what the next move is read
from. The hint trades "type to find" for the arrows exactly while the trail
overflows: typing announces itself the moment a letter lands, the scroll had
no way to.
And the find now narrows the canvas fan, not just the list. The east of the
picture is the ways onward, which is what the query is asking about; the
focus and the node we came from are the walk itself, so they hold through
whatever is typed and the graph keeps its bearings while the choices thin.
Chat. The transcript's sections are days and its section jump was already
bound, so it takes the free left pair the same way a grouped list's does --
Day up outside Day down, the lane's handedness, ctrl_pageup/ctrl_pagedown --
lit only when there is a second day to step to. Read off the two end
messages rather than by re-dating the whole transcript every paint.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The mesh walk's You had been living behind Page up, in the pager's Shift
bank, because that bank is where a screen's Home lands by default. But You
is not an end of the link list -- it drops the whole trail and refocuses our
own node -- so it had to pretend to be Top, and Bottom had to stay blank to
keep the pretence, and the walk was the one scrolling screen whose Home
didn't scroll.
So You takes a slot of the screen's own (F3, which is what F1-F3 are for),
and the pager's Shift bank goes back to meaning what it means everywhere
else: F10 Top, F9 Bottom, the two ends of the list. Home and End on a
keyboard follow the same correction and now move the highlight.
Its chord moves with it: locate is ^U, for you, on the map as well as here.
The letter is the mnemonic of the action rather than one screen's word for
it -- ^L only ever read as "locate" from inside the map's code, and the
action is the same one on both screens, so it should not have been two
mnemonics waiting to happen.
You dims once the focus is already us and no find is narrowing the list --
there is nowhere to come back from.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The map and the mesh walk echo the query you are typing in their footer
hint. The PicoCalc draws no footer hint -- the F-key lane took that row
-- so on the device both screens silently filtered themselves while the
reader had no way to see what they had typed.
The app already had an answer: the select list and the path composer
each draw the query as a warn-styled /query line above the rows it
narrows. That is now render.query_line, one definition, and the map and
the walk draw it too -- but only where the footer isn't, so the desktop
is untouched and no screen ever shows the query twice.
The row it costs comes from what the query narrows: the walk's link
list, the map's canvas. The map rebuilds its viewport a row shorter
through the ordinary resize path, so beginning a find loses a strip of
ground along the bottom rather than pushing the last row out of frame.
base_body_size was still mirroring only compose_base's bordered branch,
so a screen sizing itself against it left one row of a borderless frame
permanently blank -- which is the row the echo now wants back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Home and End are on the PicoCalc's keyboard. Paging is not. So the
jump-to-either-end pair was never the unreachable thing that earned it
F1/F2 -- it only ever wanted a chip for consistency with the pager it
belongs to. Now it rides that pager: Shift+F5 is Top because F5 is
Page up, Shift+F4 is Bottom because F4 is Page down. One axis, one pair
of keys, the second-grade verb sitting behind the first.
That frees F1-F3 on every screen, and the lane is the only place the
device can advertise anything -- there is no hint line to read a chord
off. So the freed slots went to the affordances that were invisible
there:
select lists Sect down/up, the section jumps. Ctrl+PgUp on a
keyboard with no PgUp is the one binding in the app
that was physically unreachable. Plus Delete, lit on
exactly the rows the key acts on.
map Region (Home, renamed -- it frames the ground the mesh
covers, it does not "reset" anything), You (new: Ctrl+L,
recentre on our own node at the zoom you chose), and
Frame for the find matches.
node page the tab it would switch you to.
time machine the window span it would take you to -- w was
undiscoverable on the device.
The map is the one exception to the new rule: Region answers "where
should I be looking", not "how far down the body am I", so it keeps F1
beside the other two destinations rather than hiding behind a pager it
has nothing to do with.
Screens that never scroll (every dialog, the busy splash, the reorder
list) now say so with an empty lane instead of offering a pager that
does nothing. Screens whose Home/End mean something else say what:
Latest/Oldest in a chat, Newest/Oldest in the packet viewer, You on the
walk (where End is bound to nothing, so its slot stays blank).
The map's hint line finally fits 72: its basemap status was never a key
hint, so it moved to the title's status chain where the standards put
those -- which is what took the map off the gallery's pre-existing
overflow list.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Eleven files defined the same _plain (plus test_chat's _strip_ansi and
test_timemachine's renderable flavour), and two of the copies had
already diverged into join-without-strip. tests/conftest.plain is now
the single strip-and-join reader — a list of lines or a bare string —
and every file imports it; the local variants keep their names so call
sites stay put (housekeeping H-003).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
"Atlas" named a book of maps — a thing you leaf through — and this screen has
never been that. It holds one node in focus, fans its neighbourhood, and moves
when you press Enter. The trail across the top is a walk, the list heading says
Enter walks, the tests call the helper walk_to. Only the name disagreed.
So the name goes: the tool is `walk` / "Mesh walk", the screen is WalkScreen in
ui/walk_screen.py behind open_walk, and every prose mention follows — the
README feature table, the node-vs-contact boundary in CLAUDE.md, the
windowed-list pattern note that cites this screen as its origin, pathline's
separator example, topology's whole-graph view and its prefix-coalescing
rationale, and the purge-test-data skill that warns about fake nodes showing up
here.
No behaviour moves. The tool keeps its 🌐 icon, its Mesh category and its
order beside Map — the logical shape next to the geographic one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>