Commit Graph
760 Commits
Author SHA1 Message Date
Jean-Pierre MartineauandClaude Opus 5.5 507e3bd999 In the demo, a node that we mention answers, and our node uses three-byte paths
A channel message of our node that mentions Dave gets his answer a few seconds later, and the answer mentions our node. The answers are a table in the demo script. Our node uses three bytes for each hop, as most of the mesh does, so a trace uses two.

The video's tour now selects the two paths of a message in turn, types a question to Dave so that the mention becomes a chip, and shows his answer with our star. The trace shows its new records, and the map zooms from the city to the streets around our node.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 09:51:56 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 37b17d8ba4 The demo's message paths look like a real mesh
Measured against a real Montreal database: most messages arrive twice,
through the two repeaters a companion hears, on paths that share their
start and split near the end, with 3-byte hashes. The demo did five or
six short copies through five neighbours.

Our node now hears only two new rooftop repeaters, Villeray and
Rosemont (the terrain hides the big ones), companions lose 7 dB at street
level, and each sender has its own hash width (mostly 3 bytes). Now 91%
of messages arrive twice and 83% of paths use 3-byte hashes. Noor moves
to Côte-des-Neiges to stay in reach, and the traces leave through
Villeray or Rosemont.

The capture finds the Message paths message by its text (a key press
can be lost while the transcript draws) and selects the second path,
since the first is often a straight line.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 04:30:43 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 7b8033d3ea Format the neutral greys test
One line was 102 columns. ruff format breaks it, and ruff check passes again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 03:45:59 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 4397bd58ec The demo's now is 11:42
The virtual clock of the demo script stands for 11:42 on a weekday, not
19:30. Today's lines move into the morning: Mathilde arrives at 10:05,
the smoked meat run for noon is planned at 11:20, and the dinner on
Famille moves to yesterday. A test checks that each line is sent while
its sender is heard on the mesh.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 01:25:07 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 d81f3c8618 A demo mesh over Montréal, for screenshots that stay the same
meshterm --demo runs the simulator in a synthetic Montréal mesh: 14
repeaters on the high points (Mont-Royal to Mont-St-Hilaire), rooms,
sensors, and about twenty companions, all invented. A log-distance
link model decides who hears whom, so floods arrive on several real
paths and traces walk real routes.

At each start the demo writes its own directory (<config>/demo) again:
300 days of adverts and a dense week of traffic, bilingual channel,
direct, and room conversations whose packets decrypt, traces with
their trophy-case records, neighbour tables, a joined room, admin
passwords, regions, and Watchtower alerts. Every value comes from a
fixed seed and an age before now, so each run gives the same data.
The device plays the live traffic from the same seeds.

The repository writers that stamp now take an optional time, and
record_observations stores a batch in one commit. The test world of
--mock is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 00:38:47 -04:00
Jean-Pierre MartineauandClaude Haiku 5.5 eb2d314551 Section headings carry no icon
Chat's Channels, Rooms, and Direct headings and the trophy case's
discipline headings drop their emoji, for a cleaner look. The trophy
marks stay on the delete list's rows and the record page's header.
section_heading no longer strips icons, since no heading has one.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-15 23:56:36 -04:00
Jean-Pierre MartineauandClaude Haiku 5.5 820740c9a0 A blank line before each section is one preference, app-wide
"Section spacing" (section_spacing_min_rows, in the Display group)
replaces "Author spacing" and "Spacing from". It is the smallest
terminal that gets the line: any height, 20, 24, 30, or 40 rows, or
never (default 24). Each section heading of a list gets the line, and
the chat keeps it between authors and before a new day.

The frame records the terminal rows on the screen before the body
renders (note_terminal_rows), and Screen.section_spacing is the one
test. section_heading marks its row as spaced, and the select list
adds the line, never at the top, after a blank line, or under a
heading. The hand spacers before headings are gone, except the seam
between the region table and its actions, which is two lists.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-15 22:28:23 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 30329e62a1 The border is #8f8f8f
#7c7c7c went too far. The border now sits halfway in lightness between
that and the old #a2a2a2, and on the PicoCalc it is back on the light
grey slot, the nearest one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 00:40:35 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 eeafbb18f6 Node marks are white, and the border is darker
Every node-type mark is white now: ● ▲ ■ ◉ were pink, violet, white,
and orange, and the shape already says the type. Each keeps its own
type.* style, so one can take a colour back in one place; the unknown ○
keeps its grey. On the map a crowd in one cell used to glow toward
white, which a white marker cannot do, so that signal is gone.

The border grey goes down to #7c7c7c, which JP picked on a ladder. That
sits almost on faint, so the title bar's live clip arrow is now lit like
the title instead of taking the border's grey. On the PicoCalc the
border is the dark grey slot.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 00:30:12 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 45697db856 Borders are grey
A plain border, the one with no caution tier, was the indigo accent: one
more colour on every screen, and it meant nothing. It is now its own
theme style, border, in the grey of muted, with title.border and
hint.border beside it; the amber and red tiers keep their colours. That
covers the base frame, every dialog, the handhelds' title bar, the tab
strip, and the CLI's panels. The title bar's clip arrow for a direction
with nothing more now dims to faint, since the border and muted are the
same grey. On the PicoCalc the border takes the light grey slot.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 23:21:24 -04:00
Jean-Pierre MartineauandClaude Haiku 5.5 05428bd007 Every grey is neutral
The Tailwind slate greys were cold. Each one is now r=g=b at its own
OKLab lightness, so every contrast stays where it was: the theme's
chrome, the path chips, the map's roads, labels and buildings, the heat
tail, and the emulator window. test_neutral_greys fails any #rrggbb
literal that reads as a grey and has a tint.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 23:08:27 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 7964f20889 Revert "A faded chip keeps its node's colours, with less chroma"
This reverts commit bd6df0b.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 00:55:11 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 bd6df0b9fa A faded chip keeps its node's colours, with less chroma
The part of a route that the user does not compose (a mirrored return
leg, the return home) drew every hop as the same dark slate chip. Each
faded chip now has the colours of its own node at FADE_CHROMA of their
chroma, at the same lightness, so it still says which node it is. The
PicoCalc has no colour of less chroma: there a faded hop keeps its label
colour and drops its fill. Arrows keep faint.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 00:44:17 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 796ff94dad Our star is white
The star of our node had the hue of our key, set from the self-info at run
time. It is now the white of "you" on each surface: the map, the routes,
the lists, the legends, and the chat. The run-time colour, the device-state
hook, and the style generation of the render cache are removed. A faded
star keeps its grey.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 00:18:59 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 feea5a89db In a chat, our chip is white on our fill and unknown names are black
Our chip had our hue as a light fill under dark text. It now has the fill
that each known sender gets, with a white label, and the PicoCalc can draw
that too: white on the dim twin of the hue. The hop flag is white_ink now.

A chat chip with no key keeps its grey fill, and its label is black. On the
PicoCalc it is black on the light grey of the keyless chip.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 00:06:28 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 b680b172d7 Our star takes the hue of our key, and unknown chat names go grey
The star of our node was the yellow of the map. SELF_MARK now names a
theme style, you.mark, and the theme writes our key's hue into it when the
device-state cache reads the self-info. The context makes that connection,
so the services still do not import the UI. The map, the routes, the lists,
the legends, and the chat all draw it, and the render cache keys on a style
generation so no old bytes stay. A faded star keeps its grey.

In a chat, a sender or a mention with no key had the light blue keyless
chip. It is now quiet: the grey of plain text on that grey darkened by the
chip rule. Routes keep the keyless chip, arrow mode keeps node.unknown, and
the PicoCalc keeps the keyless pair.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 23:46:39 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 3fdc638812 In a chat, a star in our hue marks a message that names us
The point of our chip did not read well. The mark at the left of the time
is now the star, in the hue of our key: the colour of the mention chip in
the body. It is the same on each platform, with chips or without them. The
Messages list uses the same star at the left of the age.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 23:23:06 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 691170d342 In a chat, the point of our chip alone marks a message that names us
The notch and the point together were too heavy. The mark is now one cell,
the point of our chip in its fill, then a space before the time. Without
chips, it is ">" in our hue.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 23:05:12 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 68d5d03324 In a chat, the ends of our chip mark a message that names us
The star at the left of the time becomes two cells in the fill of our
chip: the notch, then the point, with no label between them. Thus the mark
has the colour of the mention chip in the body. Without chips, it is ">>"
in our hue. The Messages list puts the same mark at the left of the age.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 22:42:28 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 427bee836b In a chat, our sender chip has its star inside again
Our messages go under "★ Name" on the turned-over chip, as before the last
change. A mention of us keeps its chip without a star, and its message
keeps the star at the left of the time.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 22:28:47 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 8043881d3c In a chat, the star leaves the chip and a mention is a chip as you type
Our sender chip is our name, turned over, with the star on the page to its
right. A mention of us has the turned-over chip and no star: the message
that names us gets the star at the left of its time, so every time moves
two cells right, and the Messages list puts it at the left of the row's
age. A mention chip drops its @ where chips are drawn.

The compose line draws each complete @[Name] as its chip and treats it as
one atom: the arrows step over it, and Backspace or Delete removes all of
it. A reply inserts its mention at the cursor, so one message can name
several nodes. The prompt and the typed text are white.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 21:07:06 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 0c3a8b5b5c The space between authors in a chat is a preference
Two preferences in the Display group: "Author spacing" (auto, on, off;
default auto) and "Spacing from" (the smallest terminal, in rows, that gets
the blank line with auto; default 24). The test reads the real rows of the
terminal before each paint, so a small desktop window drops the line as the
Cardputer Zero does, and a change applies at the next paint. The transcript
cache stores the spacing, so a change draws the lines again. The CLI
manual's sample of `preferences show` is the real output again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:16:15 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 1c678d43f3 An imported seed stays a seed on the uConsole and the Cardputer Zero
The key parsers now return the key in the form that the user gave. Each
device takes the form that it keeps: a firmware companion expands a seed
to the 64 bytes that it accepts, and the node of an SPI radio saves the
seed as it is, the same form as a key that it makes itself. A seed is the
more complete form, and the radio library signs with it through PyNaCl.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:10:40 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 03b3c0926b In a chat, a blank line comes after the messages of each author
The transcript asked for a gap between sender groups, but an empty Text
renders as no line at all, so the gap never showed. A blank line now
closes each author's messages, also before a new day's divider, where
the platform has 20 rows or more. The 14 rows of the Cardputer Zero keep
the transcript dense. A cached slice that starts with the gap pins its
day divider one line down.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 02:00:31 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 a19eadd81b In a chat, our messages go under our name
A channel, a direct chat, a room, and the Messages list show our messages
under "★ Name" instead of the bare star. The chip has our key's hue turned
over: dark ink on a light fill, where each other sender is light on dark.
A mention of our name takes the same chip, as "★ @Name". The PicoCalc
cannot draw a light background, so there the chip keeps the normal
colours and the star marks it. Path lines keep the bare star.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 01:56:13 -04:00
Jean-Pierre MartineauandClaude Haiku 5.5 723f7bdfef The identity key shows, imports from a file, and imports on an SPI radio
Show private key drew its warning and no key in the menu since 0.10.0.
On the uConsole and the Cardputer Zero, MeshTerm now writes an imported
key into the node's identity.key and restarts the node. An import also
reads a file (hex text or raw bytes), expands a 32-byte seed, refuses a
key of the wrong length before the confirmation, and names a firmware
that has its import turned off. The run log no longer stores the key of
config import-key (a migration removes the stored ones), and an exported
key file is written with mode 0600. Refer to #26.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-12 23:51:03 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 9ae0947717 A shared name goes to the newest key unless two keys used it within a week
Greying every name that two keys ever used was too strict: a reflash with an
erase makes a new key, and 34 names on this mesh have one, JP's own among
them. Smog went grey this morning because of two keys silent since September.

The newest key now gets the name, and the name is grey only when another key
used it within name_overlap_days (a new Display preference, "Shared names",
default 7) of it: two live claimants. A contact that was never heard never
wins. On this machine's real data, LOG.IC 3R goes to its active key 47368a7c,
Smog to fa331c83, and 8 of 883 names stay grey.

The stored names come with their times from the one scan node_names() already
ran (node_names_with_times), so opening a chat costs no second scan.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 09:49:21 -04:00
Jean-Pierre MartineauandClaude Haiku 5.5 28529d6b62 The node page shows its map preview again, except on a short frame
A contact's page has enough action rows that, on a 24-row terminal, they left
the preview under its five-row floor, and it stepped aside everywhere. It now
keeps its floor and the page scrolls. Only a short frame (the Cardputer's
11-row viewport) leaves it out.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-11 09:34:53 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 ad8792f34b A name that two nodes use resolves to no key, so it is grey
A name is a label any key can claim, and a channel message carries only its
sender's name. When two nodes use one name, the resolver used to let the last
contact win, which handed one node's hue, map, and page to a message that
nothing tied to it: a second "LOG.IC 3R" with a new key took the first one's
green and location. Such a name now resolves to None, like any unplaced name.
A contact's full key and the stored id of the same node still count as one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 04:02:17 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 2d2128718a ^O opens the author's node page, and ^G logs in to a room
In a channel, a DM, or a room, ^O on a selected message opens the node page
of its author, the same page the Contacts list opens: our own for a message
of ours, the contact the author's key finds otherwise (ContactPages, the twin
of the map's Locator). It opens to look, without the management verbs, which
belong to the list a contact is in. The hint names ^O only where it acts; the
handheld chip is Author, on Shift+F3 behind Map, since F1-F3 are full.

The room's login moves from ^O to ^G.

The Unreleased changelog now also says where the Map chip went, and covers
the shared lane and the route-end chips.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 00:25:35 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 00a7b82347 The conversations' F2 chip reads URL→QR
It names what the key does with the selection: its link, shown as a code.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 23:17:18 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 444b0ea669 A channel, a DM, and a room share one F-key lane, and the day jump leaves it
F1-F3 hold what the three conversations share, in the hint's order: Paths,
QR, Map. The Shift bank holds each screen's own verbs: ^R on Shift+F1 (Retry
in a DM and a room, Resend in a channel) and the room's Log in on Shift+F2.
F4/F5 stay the pager with Latest/Oldest behind Shift.

Day ↑ and Day ↓ are gone from the lane; the desktop keeps the day jump on
^PgUp/^PgDn.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 22:06:50 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 f07bb9e1dd The ends of a route are origin and destination chips in four more places
The Messages list's preview draws its author as an origin chip (no colon: the
point says it) and its mentions as destination chips, as the transcript does.
The packet viewer's from rows close on the point and its to row opens on the
notch, around the via chain that already opens and closes on both. The live
feed's addressed subject is an origin chip and a destination chip facing each
other across one cell of page (widgets.route_ends): a seam would claim the two
are neighbours. A pair too wide for the lane cracks instead of eliding. The
courier's recipient is a destination chip, in the hue of its key while it
waits and muted once finished.

Arrow mode reads exactly as before everywhere. A lone hop there carries no
PATH_INK stamp, so a highlighted list row whitens it like any other name.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 17:45:24 -04:00
Jean-Pierre MartineauandClaude Haiku 5.5 87baa480a9 A message's author chip closes on the point, and a mention is a chip that opens on the notch
A message goes from its author toward the nodes it names, so the two chips are
the two ends of one route that is not drawn. name_chip takes a role (ORIGIN or
DESTINATION); arrow mode ignores it, so a degraded terminal draws exactly what it
did before. A chip in a message body never breaks across lines: render_hanging
holds each whole run together, spaces included. _sender_style lost its last
caller and is gone; its tests now read the rendered chips.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-10 17:22:59 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 125ddb4003 ^L shows a message's author or a contact on the map, and ^O logs in to a room
locate moves from ^Y to ^L, and the room's login from ^L to ^O, under the
chord rule of 3675226. On the map and in the mesh walk, ^L still goes to
our node. In a chat and on a room's board, ^L opens the map on the author
of the selected message (our own message: our node); on Contacts, on the
highlighted node. map_screen.Locator holds the markers that
gather_markers returns when the screen opens and answers find() at each
paint, so the "^L map" hint atom and the Shift+F1 Map chip show only
where the key acts. A room keeps Log in on Shift+F1, so ^L has no chip
on a room's board yet.

The chat's hints for a selected message go through _fit_hint, which
drops ↑↓ and then ^P only when the line passes 72 cells; the room now
reuses them. LaneDeck.action_for returns None for a slot that has only
a Shift companion.

Right Ctrl with a letter chord still does nothing on the Canadian
Multilingual layout: JP chose not to repair that.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 17:20:42 -04:00
Jean-Pierre MartineauandClaude Haiku 5.5 db62ddf701 Enter on the live feed's pin opens the packet viewer on the pin
The viewer now opens on the stop the feed has. On the pin (^), the card
follows the stream and the feed stays pinned; on a selected packet, the
viewer holds that packet as before. Enter no longer drops the pin.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-10 01:36:53 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 2bb563605f The style and licence tests skip the files that git ignores
Both tests walk scripts/ with rglob, so they also read the MeshCore
checkout that build-firmware.sh makes in scripts/picocalc-lyra/
xiao-radio/_meshcore-build. Its third-party code and libraries have
contractions and no SPDX lines, and every developer who followed the
PicoCalc guide got three failures. conftest.not_ignored asks git
check-ignore which paths it ignores and removes them; a tracked file
is never ignored, and without git the tests examine everything, as
before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 20:26:27 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 0b764e4a04 A script builds the Debian package for the Cardputer Zero's app store
scripts/cardputer-zero/build-deb.sh runs on arm64 Debian 13 (the
Cardputer itself) and makes meshterm_<version>-<revision>_arm64.deb.
MeshTerm and all its Python packages, the spi extra included, go in
/usr/lib/meshterm, compiled to bytecode. The package depends only on
python3: the store app installs a missing dependency with apt-get but
never runs apt-get update, so a Debian dependency can fail. For the
same reason the package carries Terminus beside its OFL, because an app
from the launcher has no terminal for --fetch-fonts.

packaging/notices.py gets a site-directory mode, because the walk of
the closure asks for no extras and so misses the spi packages.

scripts/cardputer-zero/store holds the listing that czdev publish reads
and a 256-pixel icon. The screenshots are still to make.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 02:39:27 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 a594b38f21 The framebuffer test ignores the CI runner's own fb0
test_the_panel_is_written_through_its_mapping named its stand-in file fb0
and trusted that no sysfs node would answer for it. A Linux CI runner has
a real /sys/class/graphics/fb0, so Framebuffer took that device's stride,
mapped far more than the 32-byte file held, and mmap refused. It failed on
every Ubuntu job of the 0.10.3 push; Windows skips it and macOS has no
/sys. The test now stubs the sysfs read, as its comment always assumed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 01:54:31 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 73be9692b4 A channel guide lists the public channels heard on the mesh
MeshTerm already stores every channel packet it hears, including those
on channels the device doesn't have. A packet names its channel only by
a one-byte hash, but a public channel's key is derived from its name, so
a candidate name can be tested: derive the key and check the packet's
MAC with it, exactly as the firmware does before decoding. The guide
tries the device's channels, Public, every #name mentioned in a stored
message, and every known region. On one desktop history that names 25
public channels.

A guessed name has to match at least two messages before the guide
shows it. The check compares three bytes, and with thousands of names
against thousands of packets roughly one false match is expected. The
candidates are grouped by their hash byte, so each packet only checks
the ~1/256 that could match; the naive loop took 40 s, this takes 1 s
on 31,000 messages, in a worker thread under a busy card, and the result
is kept for the session while its inputs are unchanged.

"Channel guide…" is the first row of "Add a channel" on the Channels
page, and it stays there when every slot is full. Channels not on the
device come first, and Enter adds one to the next free slot. Channels
the device already has follow with a ✓ the highlight doesn't walk, and a
closing line counts the messages no name matched. Each row shows the
message count, the age of the last message and seven days of activity.
`channels guide` lists the same channels on the command line.

Refs: #25
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 01:27:11 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 2d30e8e392 Channels export to a file, and import onto another device
A new device ships with Public and nothing else, so changing devices
meant adding every channel again by hand: each private key pasted, each
# name typed, each send scope and mute set again. The Channels page now
has an "Export and import" section. Export writes every channel to one
TOML file, in slot order, with its key, its send scope and its mute.
Import reads such a file and adds its channels to the device.

Import never removes a channel. The channels of the file go first, in
the file's order, and whatever else the device holds follows them. A
channel with no free slot is reported, not forced in. Before anything is
written, a dialog shows the plan (what is added into which slot, what
moves) and ends on "Nothing is removed from the device." The slots are
read again in full before the plan is made, because a probe cut short
makes an occupied slot look free.

The file uses the same [[channels]] table as config backup, so a config
backup imports too. A # channel carries no secret, since the device
derives its key from the name. A private channel carries its key, so the
file is written readable by its owner only, and the export says how many
private keys it holds.

On the command line, `channels export FILE` writes the file and prints
its path, and `channels import --file FILE [--dry-run]` prints the
device's layout after the import, one row per channel with add, move,
keep or skip. `channels import INDEX URL` still imports a single link.

Refs: #25
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 01:15:58 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 188545cece The area drawing of a record puts a contact's position first
The Trophy case drew each hop of a walk at the first position that it
found, and it looked at the heard adverts before the contacts. The trace
scoring and the name resolver do the opposite: a contact wins. Thus a hop
could get its name and its colour from one node and its place on the
drawing from another.

On real data, the one-byte hop 3d is named YUL-Cartierville (a contact
with a position). The heard history has no position for that repeater,
so the old lookup took the next heard node that starts with 3d, a node
in St-Jean, 38 km away. Seven of 43 records drew that pin in St-Jean.

The lookup is now node_geo_lookup, a module function with tests. The
contacts are first, the same as in the scoring and the resolver.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 23:48:50 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 1914c19bf3 A test keeps the docstrings, comments, and documents in ASD-STE100
tests/test_writing_style.py rejects an em dash, a semicolon, a
contraction, or a Latin abbreviation in a docstring, a comment, or the
prose of a document. It does not read code font, quoted text, code blocks,
URLs, or legal text between <!-- ste: off --> and <!-- ste: on -->.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 21:45:04 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 efc02ddced The code text uses the glossary words in more places
A final pass over the docstrings and comments: "the own X of Y" is now
"Y's own X" (84 places), a navigation entry is a screen on the stack, our
node is our node, a paint is a paint, the quoted UX labels are in code
font, and a list of punctuation glyphs is in code font.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 21:45:04 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 11b5274b31 Docstrings and comments of the tests are in ASD-STE100
Each test docstring states the behaviour that the test proves, in the
simple present. No assertion message, test id, or other string literal
changed. Four statements now agree with the code: the Trace highlight
stays on the last row, a menu count, and two others.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 21:36:50 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 353dcb7792 A path chip is the node's name colour on a darker ground of itself
On the desktop and the Cardputer, a chip was the node's hue with dark
ink on it, so a node's colour moved from its name to the ground behind
it the moment a route drew it. Now the label is the hue, the colour the
name has in every list and in arrow mode, and the fill is the same hue
at 0.60 of its OKLab lightness (CHIP_LIGHTNESS): the rule the PicoCalc
draws with its dim twins, at full resolution. A hash label and an
annotation are the label colour throughout, with the lit bytes bold.
A keyless chip is the same pair in grey; our star, a faded hop and the
composer's white slot keep what they had.

The value came from two ladders over twelve hues. A fixed step down in
lightness made the blue fills almost black (blue is the wheel's darkest
hue), one lightness for every fill made the blue label hard to read on
its own ground, and 0.65 / 0.55 were too weak in blue / too dull in
yellow. Darkening at constant chroma leaves sRGB, and the clip merged
neighbouring greens into one #008000, so oklab.fitted lowers the chroma
instead and keeps the hue.

A thin seam between two chips of one colour is now drawn in the
previous chip's label colour, the light half of its own pair, on both
palettes; the shaded-fill rule (SEAM_SHADE) is gone with it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 03:25:13 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 69d9836546 The PicoCalc draws its path lines as chips
The console font gains the two powerline chevrons, drawn into the next
two donor slots (U+2559 and U+255B, which nothing draws), in both the
6x12 and the 6x8 builds. With them in its inventory, the PicoCalc's
verdict is core, so its routes are ribbons of chips like the
Cardputer's.

A chip there can only be filled from the eight background slots, since
under a 512-glyph font the VT has no bright backgrounds. So a node's
chip is the dim twin of its hue (theme.DIM_TWIN), and its label is the
hue itself: the colour the name has in every list and in arrow mode. A
hash label and an annotation are that colour throughout. A keyless chip
is white on light grey, the same pair one bank down. Our star, a faded
hop and the composer's slot have no dark grey to stand on, so they
stand on the page, the slot as a white + in a break of the ribbon. Two
chips of one colour join on a thin chevron in that colour.

Bold is brightness on the console, and Rich merges a selected row's
bold into every span on it, which turned the dim fills bright. Every
chip style there says "not bold"; the tests read the rendered row back
through the emulator's VT to hold that.

The new font takes effect once the font script runs again on the Lyra
(as root). Until then the PicoCalc's installed font lacks the chevrons.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 03:14:12 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 c07f15f4ec The Cardputer draws its path lines as chips
The Cardputer's panel is drawn in MeshTerm's own Terminus, which has the
powerline chevrons, yet its paths came out as arrows: the chips-or-arrows
verdict asked the terminal that started the app about its font, and on
the device that terminal is no one's, so the answer was unknown. In the
emulator it was worse, since the verdict was whatever the host terminal
could draw, a question about the wrong glass.

A handheld names its glyph inventory (Platform.font), and every frame is
folded down to that font anyway, so the inventory now answers: after the
MESHTERM_POWERLINE override, before any terminal probe. Terminus has
U+E0B0 and U+E0B1, so the Cardputer draws chips. The PicoCalc's 512-glyph
console font has neither, so its paths stay arrows, now said outright
rather than reached by not knowing. Diagnostics reads
"core (platform:cardputer-zero)". The verdict is bound on a platform
switch and its memo cleared there.

Checked by rasterizing gallery screens through the emulator's VT and
Terminus at 53x14; not yet on the device.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 02:16:44 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 b9585e9b59 Holding Esc quits wherever MeshTerm can tell the key is held
Hold-to-quit was the Cardputer's alone. It is now MeshTerm's gesture
everywhere a key can be seen coming up, which a terminal never reports:

- The emulator's window, for either handheld, which reads its own keys
  as the Cardputer's panel does.
- A terminal's Esc, where the keyboard can be asked whether it is still
  down: GetAsyncKeyState on Windows, as the right-Ctrl rescue already
  asks, and EVIOCGKEY on Linux wherever the keyboard's event device
  opens (the PicoCalc, a uConsole, a user in `input`). It reads the key
  state only, no events.

An Esc arriving while its key is still down is held back as a hold:
delivered as it comes up, a box after a second, gone at three. The
keyboard's repeats are part of the hold, a key typed during it goes in
behind the Esc, and on a VT stream the last repeat, which prompt_toolkit
can parse after the key is up, is dropped rather than going back a
screen behind a box the reader just let go of. On macOS and over SSH Esc
is unchanged.

SIGTERM is a quit for the interactive menu on every platform now, not
only under the Cardputer's launcher: a system shutting down gets the
same clean exit, terminal and console font restored.

EVIOCGKEY checked on the Cardputer's arm64 kernel; the Lyra was offline.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 01:20:53 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 9d16e0722a Holding Esc on the Cardputer shows the quit coming, and MeshTerm leaves in time
The Cardputer Zero's launcher ends any app whose Esc is held: SIGTERM to
its process group at three seconds, SIGKILL three seconds after that
(cp0_esc_exit_policy.hpp in CardputerZero/launcher; M5's packaging guide
still describes an older Home-for-five-seconds rule). Its own hint never
reaches the panel while an app owns it, and MeshTerm took 3.4 s to exit,
so a held Esc ended in SIGKILL.

Now a tap of Esc is typed on its release, and a hold of one second
floats a "Hold Esc to quit" box whose bar fills over the two seconds
left. While it is up the app holds the device's transmit lock: nothing
new goes on the air, and a scoped channel send under way closes its
window first. Letting go gives the lock back and types nothing. Held to
the end, MeshTerm leaves the way Quit does, and the launcher's SIGTERM
joins that exit instead of interrupting it wherever the main thread
happened to be.

The exit itself fits: openhop-core's cleanup joined the radio's IRQ
thread, parked in an uninterruptible 30 s poll, for its full 2 s
timeout. The node drops that daemon thread from the books first. On the
device, under the real launcher, the process group now drains 1.3 s
after the SIGTERM, with no SIGKILL.

The emulator's window does the same on a held Esc, debounced for an X
server's release-press repeats.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 00:59:38 -04:00