Commit Graph
25 Commits
Author SHA1 Message Date
Jean-Pierre MartineauandClaude Opus 5 c1db8e5a31 Cursor row: a route's hues are content, so the highlight spares them
A picked row folds its node hues to the cursor white so the row reads as one
thing rather than as a name arguing with its own selection. On a path line that
is wrong: there the hue is not decoration on a name, it is what tells one hop
from the next — and what a route graph above the row is cross-referenced by,
its labels being a marker and one byte of hash. Chips were never folded (their
fills are outside the fold's vocabulary), so the bug was the arrow form: every
path line on the PicoCalc, and on any terminal without the powerline glyphs.

PathLine now stamps every shape it mints with PATH_INK, a style that draws
nothing; Text.append_text carries the stamp into the row as a span over the
hops, and the fold skips identity spans lying inside one. So the Message paths
row keeps each hop in the colour its graph label wears, and so do the TX
screen's "Route — via", the trophy board's walk and the feed's sender → recipient
lane. The node detail's Routes tab, which bought this by hand (no cursor base at
all), is untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 00:42:24 -04:00
Jean-Pierre MartineauandClaude Opus 5 afe1424819 Selection: white says "this one", and a chip is grey
Teal was still answering three questions it had no business answering. The wordmark's
colour is the app's identity, and it is also a node hue — so a cyan-keyed node could
vanish into the very highlight pointing at it.

- A node's key-derived hue folds to the cursor white on the cursor row. A row is a
  column of identities right up until it is the row you are on, and then it is
  answering a different question; a hue on it argues with its own selection. Folded
  once at the render boundary for any Text whose *base* style is `cursor` (which is how
  every list and every screen drawing its own rows already marks it), so no screen has
  to remember the rule. `theme.is_identity_style` is node_style read backwards — every
  hue a key can mint, on both platforms — and only those fold: the heat of an age, an
  SNR reading, a red badge, a path line's chip fills all keep what they were given. So
  does the grey of a node no key could place: the highlight knows where you are, not who.

- The reverse-video chip — a dialog's committing button, a running action's Abort, the
  arrow-mode composer slot, the editor cursor — is grey. A chip marks where a press
  lands. It is chrome, and chrome does not get a hue. Light grey slot 7 on the VT: the
  one grey a reverse can put behind text there, and the fill the F-key lane already uses.

- The active sort column's heading and triangle take the same white as the cursor row,
  being the same claim one axis over. They were a bare `#22d3ee`, which put a selection
  cue back on the node spectrum and, on the console, straight onto the brand's own slot.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 03:56:35 -04:00
Jean-Pierre MartineauandClaude Opus 5 602bceea3b Theme: the cursor row is white, not the wordmark's teal
The row `❯` points at borrowed `brand` — so the app's identity ink and
"the row you're on" were the same hue, and worse, teal/cyan is itself a
node hue: a cyan-keyed node's name vanished into its own highlight.

Give the highlight its own name. `cursor` is white on both platforms
(slot 15 on the VT, the top of the bright bank, so the row's bold cannot
promote it further and every dim-slot `not bold` pin inside a highlighted
row goes on meaning what it meant). White sits outside the per-node
spectrum, so it can never collide with an identity; it does share ink
with `you`, which is the accepted cost — spans that set their own colour
keep it over the base, so only unstyled text goes white, and only on the
row already under the reader's eye.

Every surface that draws its own cursor row follows: the select list and
its reorder mode, the feed, chat, routes and actions on the node page,
the composer, records, trace, tx, the walk, the paths screen, the prompt's
suggestions. The composer's insertion-slot chip takes the same white — its
docstring already tied it to "the colour the list cursor below it wears",
and the chip ink was already picked to stay readable on white.

Reverse-video chips keep `selected`: on the VT a reversed white lands on
the light grey the F-key lane already owns, so a white default button
would stop reading as the committing verb.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 23:51:37 -04:00
Jean-Pierre MartineauandClaude Opus 5 c2640d9660 Path lines: chrome pays its own way, and every route says its length
Three things the path rows owed their readers.

The opens-further `…` was being reserved out of the lane, so a route that
fitted exactly cracked on its own last chip — the row claiming the walk ran
on when it had arrived, the crack being the closing cap with half of it taken
away. It now rides outside the budget (`with_action_mark`, and `cut_to(...,
action=True)`): the path is fitted to the whole lane and the mark takes the
leftovers, or goes unshown. That is the widget's answer everywhere a path row
carries it, not the node page's local trade.

Every stats line hanging under a path now leads with the hop count
(`hops_atom`) — the figure the picture above encodes but never states, and
the first thing two routes are compared on. `direct` where there are none,
which is the app's own word for it; the Explore list's direct-shot provenance
tag was that same word for the same reason, so the atom absorbs it.

And Explore paths cuts its unselected candidates instead of middle-eliding
them, so walking the cursor no longer rewrites the row under it. The `⋯`
rescue saves a route's two endpoints; here they are the same two on every row
by construction, so it spent cells on what the reader knows and took them off
the candidates' front. The probe-results rows pin their ranking lanes out of
the ←→ scroll while they are at it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 01:30:02 -04:00
Jean-Pierre MartineauandClaude Opus 5 7c68f50bf5 Chips: the ★ keeps its padding after all
Dropping the pads around our own star saved two cells and cost the chip its
shape — squeezed between two chevrons it read as a glyph wedged into the
seam rather than as a segment of the route. The pads are not room for a long
word, they are what makes a chip a chip.

The dark grey fill and the map's yellow ink stay.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 20:56:59 -04:00
Jean-Pierre MartineauandClaude Opus 5 1ba44ad252 Chips: one chevron, a gap for the elision, and a star that fits
Three cuts to what a chip line spends its cells on.

The seam was two: the previous chip tapering onto the page, the next
notching back out of its own fill, a sliver of bare page between. That gap
was insurance against two neighbours landing on one colour and fusing into a
single block — but the hue wheel is 256 distinct values wide (measured: zero
collisions across all 256 first bytes), so it was insurance against
something that does not happen. One interlocked chevron now, the chip behind
as foreground and the chip ahead as background.

Where two fills really are equal the interlock cannot draw a chevron at all,
and that case is not bad luck: a mirrored return leg is a run of identically
faded hops by construction, and so is a stretch of keyless greys. There the
point drops its background and the page cuts the wedge — still one cell, the
old separation without the old width.

The elision was a chip: pads, a fill, and a seam on each side, seven cells to
say "some hops are missing" on a line that is short of room by definition. It
is not a node called ⋯. It now breaks the ribbon instead — the chip before
closes onto the page, the mark sits there bare, the chip after opens on its
notch. Three cells, and elision_hop() is the one definition the widget and
the mesh walk's trail share. Arrow mode needed no change: a bare label
between two arrows already was a gap.

The you chip was ` ★ ` in dark ink on pure white — five cells shouting the
one node the reader never has to be told. Now the map's own yellow star
(taken from marks.SELF_MARK, so the star in a route and the star on the map
cannot drift) on a neutral dark grey, and padless, because the pads exist to
keep a *word* off the chevrons and a single mark centred in its cell needs no
such room. Three cells.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 20:37:29 -04:00
Jean-Pierre MartineauandClaude Opus 5 6a12104cf7 Path lines: cut them all the same way, and end them where they end
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>
2026-08-09 18:36:43 -04:00
Jean-Pierre MartineauandClaude Opus 5 9e262d157d Path lines: a cut chip cracks, it doesn't trail off
An ellipsis at a path line's edge says a word was shortened. What actually
ran out is the lane — the route continues, and in chip mode there is a
segment right there to say so with. A cut chip now breaks off on a half
block drawn in that chip's own fill and no background, so the cell is half
segment and half bare page: the crack reads as a chip that carries on.
CRACK_TAIL (▌) closes a line the path runs past, CRACK_HEAD (▐) opens one
whose start is off to the left.

Only chips can crack, which is also the whole mode test: cut_mark reads the
fill from the nearest cell still drawn — scanning back for a tail cut,
forward for a head cut, so a cut landing on a seam still takes the colour
the reader can see — and falls back to the muted ellipsis when it finds
none. An arrow line has no fill to shear, so it keeps cutting exactly as
before and no caller has to know which mode drew the line.

Applied at the four places a path line is cut rather than folded: the
ellipsized() last resort, the over-wide lone hop in wrapped(), and the two
h-scroll surfaces whose edge marks were literal "…" — the Message paths
selected row and the node detail Routes row (whose unselected rows now go
through cut_to as well). Untouched: the ⋯ that marks whole hops elided out
of a path's middle, which is a different claim entirely.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 18:09:19 -04:00
Jean-Pierre MartineauandClaude Fable 5 6a79ec5b5e Colour marks an identified node: unknown hashes go grey
A hash whose node nobody can name reads whole in node.unknown — path
lines, key lanes, the walk, the packet card, the arrivals — never in a
hue its bytes would derive, exactly as the path graph already drew it.
highlighted_hash gains a known flag; the two path widgets decide it from
their resolver, and the node page threads it per hop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-08 19:11:12 -04:00
Jean-Pierre MartineauandClaude Opus 5 d79d85b7ee An unknown node is light grey, and bold stops recolouring it
Two reports on the PicoCalc's message paths, one root cause each.

The graph drew an unnamed hop's hash — and the sender's name — as white,
the "you" colour. MapCanvas emits its own bold for markers and labels,
and its colours arrive already quantized to a palette slot; the kernel VT
then draws bold as *brightness*, so anything that landed in the dim bank
was promoted to its bright partner. Light grey became white. A purple
repeater would have become pink the same way. The canvas now drops
emphasis where bold is brightness — the colour is the load-bearing part.

The same trap reaches the theme through inheritance: Rich merges a row's
base style into every span, so a dim-slot span left silent about bold
changes colour inside a selected row. Every dim-slot style now says which
it means — `not bold` to keep its colour, `bold` where the promotion is
the point (only title.muted) — and a test forbids staying silent.

The pathline drew the same hop dark grey, because `muted` collapses onto
the console's dark grey while the desktop's is light. That conflated two
things: `muted` is chrome and may recede a step, but a node we can't
identify is content you can still act on. It gets its own name,
`node.unknown` — light grey, identical to today's desktop muted, so
nothing moves there — and it is what `name_style` returns keyless, what
an unlit `highlighted_hash` wears, and what the `○` ring is.

Also fixes a crash from the previous commit: the graph's label colour
resolved a marker through `parse_hex`, which the type marks' theme names
are not. A repeater relay we couldn't name would have raised.

The gallery's node-detail header spelled a marker hex by hand; it goes
through the app's own styles now, so a palette bug can't hide behind it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 21:06:42 -04:00
Jean-Pierre MartineauandClaude Fable 5 84caae53f8 P3 rework: the PicoCalc visual language, done properly
The first P3 (a84abf3) ran on the wrong model and its palette never
reached the TUI: MESH_THEME_16 was written in 256-colour cube indices
instead of the 16 VT slots, and the platform theme pick was wired into
the scripted-CLI console while the real rasterizer stayed hardcoded to
truecolor. Replace it end to end:

- theme._VT_SLOTS: the canonical slot->RGB design (conventional hue
  families kept; slots 5/6 become the track/faint slates), MESH_THEME_16
  rewritten against color(0..15) with the VT's bold-as-brightness and
  dim-bank-background rules pinned by tests. /etc/vtrgb + a boot oneshot
  install it (console-font script), OSC fallback when kbd is absent.
- platforms.on_platform: set_platform now re-runs registered bindings,
  so theme, rasterizer and widgets swap implementations once per switch
  and hot paths read module globals - no per-call get_platform().
- The render-boundary fold (theme.fold_text): one str.translate pass
  (NFKD accent folds - the Terminus base has no accented Latin - plus
  symbol stand-ins and the width-padded emoji map), an embedded-SGR
  quantizer for canvas truecolor, and a ?-at-own-width catch-all that
  makes the no-wide-glyph short-circuit true by construction. Applied at
  the only two places every visible byte passes: render_to_ansi and
  MapCanvas.to_ansi_lines.
- ui/fontset.py freezes the installed font's inventory (dumped live);
  the gallery now asserts rendered picocalc ANSI carries no truecolor/
  256-colour SGR and no character outside it - the parity gate the
  first attempt faked by asserting a dataclass field.
- Font build: nine new 6x12 marks (ellipsis, warn, backspace, shift,
  gear, refresh, clock, crosshair, padlock) drawn into donor slots, with
  donor misses now fatal and a keeper canary (donating pi once erased
  Cyrillic pe - shared slot). Table re-dumped on-device: identical to
  fontset.py, 519 = 519.
- Type-based name colouring: registry moved to core/nodetypes (services
  no longer import ui), fed from monitor observations, heard_nodes and
  merge_contacts. Heat gradient quantizes to heat.* steps on picocalc.
- ui/marks.py: the P2-deferred constant relocation - widgets no longer
  drags map_render/mapcanvas/pathgraph for tuples and aliases.
- meshterm specimen: the visual language on one card through the real
  funnels - the on-device acceptance screen (JP's request).
- tools/chat: delivered/no-ack state uses the standard check/query
  marks, not the banned emoji pair.

Verified on the Lyra: font+palette installed, specimen framebuffer
captures in .claude/plans/. 1259 tests green, 13 xfailed (P6 list).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 12:48:07 -04:00
Jean-Pierre MartineauandClaude Opus 5 65a47af037 Trophy case: the cells belong to the walk
A board of records is a list of routes, and the routes were the part with no room
left. Every row spent thirty-odd cells before the first hop — `#1  Jul 12 14:30`,
a score padded to a fixed twelve — and then opened the walk by naming our own node
in full, `Homestead (cc)`, twice, on every row, on a screen where every record
starts and ends on us by construction.

So the rows draw on THE path widget with both ends bare: the app-wide ★, which the
graph on the record card already plants on us and its legend already reads back as
"you". The stars keep the `you` white — path_line's fade means "not yours to
compose", and nothing on a board of walks already made is being composed, which is
what `dim_self` now says. The lanes in front are held to what they actually say:
the day the record was set (the card carries the time), and a score lane fitted to
the widest score on *that* board rather than to the worst case everywhere.

The record card was carrying the same kind of slack. Its route ran under a `route`
label lane, hanging in the twelve cells left over — under a graph captioned
"you → … → you", where the line can only be the route. It now runs unlabelled
across the whole card, wrapping at hop boundaries, on the same widget as the rows,
with the same ★ at both ends.

And the card was three rows taller than its content. The route graph took the
shared widget's five-row floor, which reserves room for a fan of alternatives; one
walk is one lane and never fans, so it draws in three — and both stacked blocks now
hand back the rows nothing landed on. The area drawing keeps two rows in hand for a
tall shape, and a walk that comes out flat never reaches them: that, plus the
graph's unused label row, is where the three blank lines under `round trip` came
from. Trimming happens after the drawing, so nothing placed is ever lost.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 04:13:44 -04:00
Jean-Pierre MartineauandClaude Opus 5 722c8f0e4f Mesh walk: the atlas is a walk, everywhere it's named
"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>
2026-07-28 20:59:25 -04:00
Jean-Pierre MartineauandClaude Opus 5 b76f50d6f4 Mesh atlas: the trail is a path line, snapped to where you are
The breadcrumb across the top was hand-rolling its own hop sequence — its own
separator loop, its own width arithmetic, its own head-dropping. A walk *is* a
path, so it now renders through PathLine like every other hop sequence in the
app: powerline chips wherever the terminal draws them, the trail's own › arrows
where it can't, each hop in its node's key-derived hue.

It stays one line — no wrap, no scroll. When the walk outgrows the width the fit
eats into its head rather than a route's usual tail, and what survives snaps
flush to the right edge, so the focus and the steps that just led to it are the
ones always in view.

PathLine.ellipsized grew the parameter that makes that possible: `elide` names
the side the ⋯ eats into. ELIDE_TAIL (the default, unchanged behaviour) anchors
on the head and rescues the destination off the far end, so both endpoints of a
route survive; ELIDE_HEAD anchors on the tail and spares the origin nothing.

Along the way the trail stops losing its colour: the old focus emphasis built
"bold you" / "bold muted" style strings, which Rich can't parse and silently
renders as no style at all — so our own node at the root of the walk was drawing
plain instead of white. Identity styling now comes from the hops themselves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 19:50:01 -04:00
Jean-Pierre MartineauandClaude Opus 5 50a3322b84 Live feed: rows slide sideways; the viewer's via folds
Three surfaces, one thread — a route should be readable where it is,
not only where it fits.

The feed's highlighted row now scrolls horizontally under ←→ (the
app-wide hscroll convention: the ▸ pointer lane pinned, the shift
abandoned the moment the selection moves on, the footer atom surfaced
only while the row overflows). A selected row carries its relay chain
whole so the scroll has something to reveal; every other row keeps
eliding its middle hops, so both ends of the route stay in view.

The packet viewer's `via` row leaves `path_text` for THE path line, and
folds instead of running off: a PathLine handed to Rich now renders
itself as the wrapped shape at whatever cell width the layout works out,
so a value whose lane is decided by the *other* rows' labels no longer
has to compute that width twice. Hops break at their own boundaries and
hang under the value block.

And the viewer's `from` row leads with the shared node-type mark — ★ us,
▲ repeater, ■ room, ◉ sensor, ● node, ○ when neither the packet nor the
contacts can type it — so a sender reads as the same kind of thing here
as it does on the map, in the contact list, and in the route graph three
rows down.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 00:16:22 -04:00
Jean-Pierre MartineauandClaude Opus 5 c537fa971e Path lines: the cursor is a hop; message paths read as names
The composer's insertion point was a reverse-video arrow living between two
hops, which forced the whole preview back to plain arrows — chips fuse the seam
it needed to sit in — and made a wrapped route carry the cursor by hand, riding
a line's trailing cue when the break landed on it.

An insertion point is a position in the route, so it is now a hop of its own: a
`+` slot in the brand accent (the same colour the row cursor below it wears),
spliced in by `path_line(cursor=…)`. It measures, wraps, and colours like every
other hop, so the preview keeps whichever mode the terminal earned and can never
strand its cursor on a fold. `cursor_arrow` goes with it, out of both `PathLine`
and `path_text`.

Message paths: the arrival rows draw through the path line too — chips in each
node's hue, names alone, no hash repeated after one. The hash bytes are the
graph's job one row up, and the shared node hue ties the two together without
spelling the hex twice. The graph loses the blank line above it; its canvas
already opens with air over the topmost lane.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 21:16:47 -04:00
Jean-Pierre MartineauandClaude Opus 5 fc9a459eda Path lines: notch every chip's left edge; merge duplicate hops
The gap between chips was one-sided: the previous chip tapered out to a
point onto the page, but the next one started on a flat edge. Both faces
now angle the way the path flows — the seam is two cells, a taper out and
the same point notched back inward out of the next chip's fill, with a
sliver of bare page between them. It is the notch a wrapped line already
opened on, so a fold now looks exactly like the seam it interrupted.

The composer's suggestion list could show one node twice. The evidence
graph keeps a hop hash it can't pin down as its own vertex — a 1-byte "3d"
matching two contacts stays "3d", apart from the "3d63c6429436" its wider
sightings landed on — and it is right not to guess; but once the name
resolver names them, they are one row drawn twice. They now fold onto the
longer id with their readings pooled, so the row's sample count and median
SNR describe the whole node. Merging takes both a shared name and a shared
prefix, so two unnamed hashes never fold and neither do two same-named
nodes on unrelated keys.

Folding the stub away must not fold its links away with it, so a step off
the merged node gathers from every id it reads as — otherwise merging would
quietly cost the composer whatever was only ever heard under the stub. Name
lookup and that alias walk are both memoized: they now run over the graph
on the way to each repaint, and neither the topology nor the resolver moves
while a composer is open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 20:28:18 -04:00
Jean-Pierre MartineauandClaude Opus 5 2c2f13ce41 Path lines: gapped chips, both ends automatic, routes name nodes
The trace surfaces' route lanes were spending their width on hex nobody
reads there: every name carried the hash it is addressed by, and the wire
spec one lane below already spells that out verbatim, at the width it goes
on the air. The route lane, the planned-route preview and the composer's
live preview now name nodes and nothing else; an unnamed hop still reads as
its hash, since that is the only identity it has.

The composer's preview becomes that same lane rather than its own dialect —
pathline chips, hop-boundary wrapping, our two ends the bare star.

Both stars fade. Setting out from us and landing back on us are fixtures of
every such route, no more composed (or removable) than a mirrored return
leg, so they wear the automatic grey — dark slate as a chip, never the loud
you white. Finding the turn a wrapped route folds at now reads the faded
*tail* rather than the first faded hop, which the leading star had become.

Chips are separated, not interlocked: every seam draws its point with no
background, so the page shows through the taper and a route reads as a row
of blocks the way oh-my-posh spaces its segments. Two neighbours landing on
one fill can no longer fuse, without that being a special case.

Finally, two places that had the right information and didn't use it: the
composer's suggestion list fell back to bare hex for a short hop matching
several contacts (the topology won't guess, so it names nothing), and the
not-a-trail confirm printed whole 12-hex ids. Both now go through the
owning screen's own resolver and its chosen path-hash width, so a node
reads the same in the dialog as in the window behind it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 19:05:43 -04:00
Jean-Pierre MartineauandClaude Opus 5 edbd7cdcfa Path lines: step a wrapped line in past its hanging indent
A folded route hung flush under the value column, so a two-line route and
two stacked values looked alike at a glance. Continuations now step in
WRAP_OFFSET columns past the indent; the packer charges for the step, so
every line still fits its width. The continuation marks — the trailing
arrow, the notched chip edge — still say it in detail; the step says it
from the block's shape alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 18:33:09 -04:00
Jean-Pierre MartineauandClaude Opus 5 141e3c9e51 Path lines: a star for us, a clean notch, rounded outer ends
bare_self now stands our node on the app-wide ★ rather than stripping it
to nothing, so both ends of a route are marked, not merely implied. The
star we set out from wears the you white; the one we land back on fades,
because coming home closes every route automatically — nobody composed
that leg, same reasoning that dims a mirrored return. A lone faded tail
no longer counts as the route's turn, so a walked boomerang still folds
at its own middle.

The wrapped line's opening cap carried the previous chip's colour into
the new line. It now draws the point in the line's own fill under reverse
video, which paints the notch in the terminal's own background — the only
way to name the page's colour without knowing it — so the carry-on mark
holds no trace of the chip above.

And the extended powerline block finally earns its detection: where the
font is a full Nerd Font patch (powerline_full), a path's two outer ends
round off into a lozenge. Core-only terminals square the opening and
point the close exactly as before, so nobody sees tofu.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 18:25:33 -04:00
Jean-Pierre MartineauandClaude Opus 5 c393aabc52 Path lines: the point always faces the way the path flows
A wrapped line was opening on U+E0B2, which points backwards — it read as
an arrow going the wrong way, not as a carry-on. It now reopens the seam
the break interrupted: the same right-facing point, arriving out of the
previous line's last fill into this line's first chip. U+E0B0 is once
again the only glyph the widget draws; what varies is whether the point
lands on a field (a seam) or on the page (a closing edge), which is also
the same-fill gap rule, now factored into _seam().

A bare hop keeps the one-cell padding every chip wears — it has no words
between them, not no chip — so our end of a route reads as a stub of the
you white rather than as nothing at all.

Explore's candidate paths go bare too: every candidate starts from us, so
the word was the same on every row and the cells are the candidates'.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 17:20:55 -04:00
Jean-Pierre MartineauandClaude Opus 5 c7ad0c4ad3 Path lines: open the seam where two chips share a fill
An interlocked seam is the point in the previous chip's fill drawn on the
next one's — invisible when the two fills match, so a hue collision, a run
of dimmed hops, or two keyless greys read as one fused block instead of
two nodes. Those seams now draw over the page instead, leaving the sliver
of background that says where one chip ends and the next begins.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 17:06:50 -04:00
Jean-Pierre MartineauandClaude Opus 5 7c0c2c5a07 Path lines: bare us endpoints, female point opening a wrapped line
A hop with no label now draws as its seam alone — the arrow, no chip, no
word. The trace screens' route lane uses it (bare_self) for both ends of
every walk: a trace leaves us and comes back to us, so naming ourselves
twice only pushed the hops that *are* news out of the lane. Arrow mode
drops the padding the vacated label sat in ("→ 3d → f2 →"); chip mode
lets the seam point into the page.

Wrapped chip lines now open on the female point (U+E0B2, core set) from
the second line down, so a continuation reads as one — only the very
first chip of a path keeps its square left edge. The packer pays for that
cell, so lines still fit their column.

Also carries the round already sitting in the tree: the wrap evens hops
across the fewest lines it can take and folds at the route's turn, the
trace/node-detail/select surfaces route their rows through the widget.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 17:04:49 -04:00
Jean-Pierre MartineauandClaude Fable 5 2c04fb5b96 Path line: path_text-parity factory, cursor-aware wrap, plain pin for tests
path_line() builds a PathLine from path_text's exact vocabulary (verified
character-for-character in tests), wrapped() carries the composer's
insertion cursor — riding the trailing arrow when its seam is the line
break — and conftest pins MESHTERM_POWERLINE=0 so screen assertions never
depend on which terminal runs the suite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 20:30:50 -04:00
Jean-Pierre MartineauandClaude Fable 5 13dd79018d Path line: flexible hop-sequence widget, powerline chips or arrows
PathLine renders PathHop sequences three ways (full line, middle-elided to
a width, hop-boundary wrap under a hanging indent) in two looks: the
app-wide muted-arrow presentation, or interlocking powerline chips filled
with each node's key hue when the terminal can draw U+E0B0 (auto mode asks
termfont). Same words in both modes; call sites migrate separately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 18:27:20 -04:00