Commit Graph
1037 Commits
Author SHA1 Message Date
richandClaude Opus 5.5 ba091ed586 The reaction dialog searches every emoji by name
CI / lint (push) Canceled after 0s
CI / test macos-latest py3.14 (push) Canceled after 0s
CI / test ubuntu-24.04 py3.10 (push) Canceled after 0s
CI / test ubuntu-latest py3.11 (push) Canceled after 0s
CI / test ubuntu-latest py3.12 (push) Canceled after 0s
CI / test ubuntu-latest py3.13 (push) Canceled after 0s
CI / test ubuntu-latest py3.14 (push) Canceled after 0s
CI / test windows-latest py3.10 (push) Canceled after 0s
CI / test windows-latest py3.14 (push) Canceled after 0s
CI / build (push) Canceled after 0s
Issue trailers / check (push) Canceled after 0s
^E still lists the reaction_emoji preference first, under "Your reactions". Under
"All emoji" it now lists each other emoji with its Unicode name, and the list
filters as you type: "fire" finds 🔥, "heart" finds the hearts. The catalog comes
from the Unicode database of the Python that runs MeshTerm and from Rich's table of
text-default emoji, so it needs no data file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:09:20 -04:00
richandClaude Opus 5.5 766b37b56d A channel sender's name can have 31 characters, so a long name no longer breaks reactions
The firmware keeps a node name in 32 bytes, but the sender prefix and the @[Name]
mention matched only 20 characters. A longer name stayed in the body: the transcript
showed no sender for it, and its reactions showed as new messages. Found on hardware
with "Hazelwood r0b3.Anduril" (22 characters).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:04:21 -04:00
richandClaude Haiku 5.5 b65beef7db Chat messages take emoji reactions, in the wire format of MeshCore One
Select a message in a channel or a DM and press ^E (Shift+F2 on the handhelds) to
react with an emoji. A reaction is a usual message on the wire: MeshTerm writes the
format of MeshCore One and reads it and the two formats of meshcore-open. The
transcript folds each reaction under the message that it names, with a count, or
the names of the nodes while that message is selected. A reaction does not raise
the unread badge, and the Messages list previews it as "reacted 👍".

A reaction names its target by the sender's timestamp. Thus MeshTerm now gives the
device the timestamp of each message it sends and stores exactly that second. A
retry of a DM keeps the timestamp and counts the try number, as the protocol
specifies. The emoji that ^E offers are the reaction_emoji preference. Rooms have
no reactions.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-14 21:49:31 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 488db7f278 MeshTerm 0.11.1
Installers / cardputer-zero-deb (push) Canceled after 0s
Installers / windows-x64 (push) Canceled after 0s
Installers / macos-x64 (push) Canceled after 0s
Installers / macos-arm64 (push) Canceled after 0s
Installers / linux-x64 (push) Canceled after 0s
Installers / linux-arm64 (push) Canceled after 0s
GitHub release / build (push) Canceled after 0s
GitHub release / create the release (push) Canceled after 0s
GitHub release / send the Cardputer package to the app store (push) Canceled after 0s
GitHub release / put the version on meshterm.net (push) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
v0.11.1
2026-10-13 00:06:30 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 b434cb1643 The changelog names #26 under the identity key fixes
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 23:53:36 -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 7209d89911 The store job brings JP's fork of the store up to date before it publishes
czdev starts its branch from the fork's default branch, and the store's
check compares the PR branch with the store's main directly. After
M5Stack removed every app on 2026-10-12, the fork was 10 commits behind,
so the 0.11.0 PR (#176) carried all 40 removed apps as new files, and
the check failed on the first of them. store-publish.py now calls
GitHub's merge-upstream for the fork first, and publishes nothing if
that fails. PR #176 was repaired by hand with "Update branch".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 10:57:20 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 1853e5e603 MeshTerm 0.11.0
Installers / cardputer-zero-deb (push) Canceled after 0s
Installers / windows-x64 (push) Canceled after 0s
Installers / macos-x64 (push) Canceled after 0s
Installers / macos-arm64 (push) Canceled after 0s
Installers / linux-x64 (push) Canceled after 0s
Installers / linux-arm64 (push) Canceled after 0s
GitHub release / build (push) Canceled after 0s
GitHub release / create the release (push) Canceled after 0s
GitHub release / send the Cardputer package to the app store (push) Canceled after 0s
GitHub release / put the version on meshterm.net (push) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
v0.11.0
2026-10-12 10:49:12 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 b4b75008d2 The Cardputer Zero is supported, and its page says how to install it from the store
The page no longer warns that the handheld is not supported. It now gives
three ways to install MeshTerm: M5Stack's app store first, then the same
package from a GitHub release, then the source code for developers. The
README, the docs index, and the hardware guide say the same, and the
hardware guide says that the launcher selects the platform.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 10:49:12 -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 Opus 5.5 3675226a40 A Ctrl letter may carry another verb on a screen that does not use its own
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 16:19:17 -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 c8f7e7ec48 The store listing has its title, summary, and description in four languages
The Store app on the Cardputer knows English, simplified and traditional
Chinese, and Japanese, and shows English for any other language. It falls
back to en and then to zh-CN, so the locales carry en too: without it, an
English user would see the Chinese text. PR #164 already has the same
locales, so later releases keep them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 20:29:05 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 0d08e4bb5e The PicoCalc's radio recovers from a broken frame, and its flash path works as the guide says
The XIAO stopped answering on 10-07 ("No response from meshcore
node" for many starts in a row). MeshCore's serial parser reads '<'
and a two-byte length, then waits for that many bytes with no time
limit, so one lost or damaged byte can make it swallow up to 65,535
bytes, and MeshTerm sends only 16 per start. Reproduced on the
PicoCalc: a half frame blocked two APP_STARTs until 600 filler bytes
arrived. meshcore-frame-timeout.patch drops a frame after 200 ms of
silence on the line (bytes queued while the loop was busy still
count), and build-firmware.sh applies it after meshcore-uart1.patch.
Upstream dev and main still have the old parser.

Flashing the XIAO with the new firmware, following the guide as
written, found these problems, now corrected:

- Step 11: `pip install platformio` failed (no pip outside a venv on
  this PC), and it fails on externally managed Pythons or lands in an
  active venv. The guide now uses the official installer only, and
  build-firmware.sh and flash.py find pio in ~/.platformio/penv, so
  nobody edits the PATH. uf2conv runs on PlatformIO's Python, because
  a bare `python` is missing on many Linux systems.
- Step 12: the script's last line said to double-tap reset first;
  flash.py touches the XIAO itself, and a wired XIAO's reset button is
  inside the case.
- Step 13: this XIAO's bootloader has only a serial port, and flash.py
  printed a pio command for the user to run "from your MeshCore
  checkout", with pio not on the PATH. flash.py now does the serial
  DFU upload itself (54 s here). Without pyserial, it runs itself
  again with PlatformIO's Python instead of skipping the touch.
- A wired XIAO's USB power goes backward into the Lyra through the
  3V3 wire (JP confirmed it on the bench). Step 13 now applies only
  before wiring, and a new Day-to-day procedure updates the firmware
  of a wired radio: poweroff, switch off, open, batteries out, flash.

After the flash, the radio kept its identity, MeshTerm's info command
connected, and an APP_START after a half frame and 0.5 s of silence
got its reply at once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 20:26:39 -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 3f7180b866 The store job waits while a PR for an earlier version is open
A release before M5Stack merges the first store PR would open a second
PR that adds the same files, so the two would conflict and both would
wait for a person. store-publish.py now publishes nothing while any
open PR of ours is for the package, and in CI it shows a warning. The
release itself goes out as usual. After the merge, a rerun of that
release's store job publishes its version; the release skill says how.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 04:40:49 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 c5e7ce5a62 A release sends the Cardputer Zero package to M5Stack's app store
installers.yml gains a cardputer-zero job: build-deb.sh in a Debian 13
container on an arm64 runner, the same system as the Cardputer. The
release attaches the .deb with the other downloads and lists it in
SHA256SUMS, and its notes say where Cardputer users get MeshTerm.

github-release.yml gains a store job after the release. It runs
scripts/cardputer-zero/store-publish.py, which calls czdev publish with
two corrections: the manifest's email is johnputer@meshterm.net, not
the GitHub profile's, and the branch name stays the same between
czdev's two calls (for a fork, the second call named a branch that was
never pushed, and GitHub refused the PR). It also sends czdev's git
over HTTPS with the token, and it publishes nothing when the store has
the version or a PR for it is open, so a rerun opens no second PR.
AppBuilder is pinned to the commit whose functions it corrects.

The job needs the CARDPUTER_STORE_TOKEN secret (public_repo scope). The
release skill now checks the store PR and counts fifteen assets.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 04:32:35 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 cc626160d4 The store listing has its six screenshots, taken on the Cardputer
scripts/cardputer-zero/screenshot.py reads the display's framebuffer
over SSH and writes it as a PNG of 320x170, the size the store wants.
It only reads, so it does not disturb the app. JP set up each screen
on the device: the splash, the menu, the Public channel, a contact's
routes, the map, and a packet from the live feed. The listing passes
the store's own metadata check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 03:00:19 -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 93ed530fd7 The checkout's launcher entry has its own name and a negative icon
The store package will own meshterm.desktop and meshterm.png. The
entry that launcher-entry.sh makes is now meshterm-dev.desktop with a
negative of the logo, so that the two look different and can be
installed together.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 02:39:02 -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 d3d2dc33b7 The Records screen docstring records the guess for a short hop
A hop that the topology could not resolve keeps its short hash, and the
area drawing places it at the first node that matches. The score leaves
such a hop out. JP chose on 2026-10-07 to keep the guess: the probable
shape is more useful than a shape that has lost a point.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 01:50:04 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 f44e007902 MeshTerm 0.10.3
Installers / windows-x64 (push) Canceled after 0s
Installers / macos-x64 (push) Canceled after 0s
Installers / macos-arm64 (push) Canceled after 0s
Installers / linux-x64 (push) Canceled after 0s
Installers / linux-arm64 (push) Canceled after 0s
GitHub release / build (push) Canceled after 0s
GitHub release / create the release (push) Canceled after 0s
GitHub release / put the version on meshterm.net (push) Canceled after 0s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
v0.10.3
2026-10-07 01:49:33 -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 eb55b51a12 The simulator refuses a channel slot the way the companion does
MockDevice raised DeviceCommandError for an index past its last slot.
The slot probe reads that error as a read that failed, not as the
firmware's "no such slot", so a probe on the simulator never finished.
The session never cached the slots under --mock, and anything that
insists on a complete read could not run there. The simulator now raises
the same RuntimeError that the companion's _ok raises for
ERR_CODE_NOT_FOUND, and the probe ends cleanly.

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 8cc111b2e8 CLAUDE.md names the older words that the glossary replaced
CLAUDE.md stays dense, because it loads in every session. It now says
which of its words the code text and the documents replace: the reader
is the user, a popup is a dialog, a frame on the stack is a screen on the
stack, and a truncated key is a key prefix.

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 4e22f29a74 The exit status docstring says what grep does
It said that grep treats "found nothing" and "worked" as the same
result. grep exits with 0 when it finds a line, with 1 when it finds
nothing, and with 2 on an error, so it is the usual exception. The text
now says so, and it says why MeshTerm uses 5: 1 is already FAILURE.

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 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 15c1df20d7 The style guide states the rule for tool comments and quoted labels
The tool part of a tool comment stays exactly as it is, and the
explanation after it is prose in STE. A quoted UX label is in code font or
in quotation marks, not in italics.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 21:45:47 -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 bc51115d24 The changelog is in ASD-STE100
Each version, date, fact, and link stays. The version headings use the
Keep a Changelog form (## [x.y.z] - date); the release workflow reads only
the bracketed version, and each section still extracts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 21:44:55 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 26d422e651 The CLI manual and the cookbook are in ASD-STE100
Each option, field, unit, exit status, and example output stays exactly
as it was.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 21:44:55 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 c0e7c17252 The guides and the device manuals are in ASD-STE100
The four user guides and the manuals for the PicoCalc, the uConsole, and
the Cardputer Zero. Procedures are numbered steps, with a WARNING or a
CAUTION before each step that has a risk. The step headings use a colon,
and each link to them changed with them. Quoted error messages are in
quotation marks, and the menu question is quoted as the app shows it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 21:44:55 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 e6f6cf2621 The README and the project documents are in ASD-STE100
The README, CONTRIBUTING, SECURITY, the docs index, the development
documents, the script READMEs, the comments of config.example.toml and
pyproject.toml, and the issue and pull request forms. The legal sections
of the README (attribution and licence) stay exactly as they were, between
ste: off and ste: on markers. The SignPath attribution line, the one-line
description of MeshTerm, every code block, and every URL do not change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 21:44:55 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 962d96f8d3 The writing standard is ASD-STE100
Docstrings, comments, documents, guides, and the changelog are written in
ASD-STE100 Simplified Technical English, with Canadian spelling.
docs/development/writing-style.md has the rules, the six rules that we
bend, the conventions for docstrings and comments, and the glossary: one
word for each concept, an extension of the lexicon in CLAUDE.md. CLAUDE.md
now points to it and says which texts are not in STE. The release skill
writes the changelog in STE, under headings in the Keep a Changelog form.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 21:44:54 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 99b991707e Comments of the scripts and the packaging files are in ASD-STE100
The Python scripts, the packaging code, and the full-line comments of the
shell scripts. Heredocs, marker lines, licence lines, and code do not
change. The help text of type-text.py now starts with a complete STE
sentence, and uart1-mux.py now says three writes, as the code does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 21:37:03 -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 981f4527e9 Docstrings and comments of the path line, theme, and font modules are in ASD-STE100
pathline.py, theme.py, termfont.py, fontset.py, widgets.py, and oklab.py.
These were done last, after the path chip work in them was committed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 21:36:49 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 5752bf7cf6 Docstrings and comments of the top-level meshterm modules are in ASD-STE100
cli.py, context.py, platforms.py, and __main__.py. The package
docstring (the fixed description of MeshTerm) does not change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 20:37:27 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 abfd32c491 Docstrings and comments of meshterm/ui are in ASD-STE100
The same rewrite as for the core package, for the screens, the dialogs,
the map, the charts, and the CLI renderers. The glossary words are used
for the screen: the user (not the reader), dialog (not popup), viewport,
list window, highlight, pointer, graph lane, marker, raster, result
screen, and key prefix (never hash for a truncated key).

The rewrite also corrects approximately 70 docstrings and comments that
no longer agreed with the code: rows that no longer exist (Back rows),
counts that changed, renamed constants, wrong cross-references, and
behaviour that changed. Each correction follows the code; no code
changed.

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