10 Commits
Author SHA1 Message Date
richandClaude Haiku 5.5 a323342971 Add 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-15 07:47:13 -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 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 1ceda7e22f Chat joins room servers, and reads each post under its author
A room server is a message board on a radio: it keeps its members' last
posts and sends each member the ones they missed. MeshTerm could see rooms
and run your own, but not join one. Posts that reached the radio were filed
as direct messages from the room, with their authors thrown away.

Chat now has a Rooms section between Channels and Direct. Opening a room
shows its board at once, each post under the member who wrote it. A post
carries the first four bytes of its author's key, so it is named by any
contact or overheard advert with that key and coloured by the key itself.
An author nothing names stands as the hash, in the unknown-node grey. The
first visit asks for the room's password over the board. After that,
opening a room logs in again only once it has been quiet for
room_relogin_minutes (a new preference, 30 by default), and ^L (F6 on the
handhelds) logs in on demand. The title says what the room allowed:
member, admin or read-only. A read-only member gets a line saying the room
drops their posts, in place of a compose box. A post goes out once, never
soft-retried, since a duplicate would reach every member.

Underneath: messages carry an author (schema 18), a room's board leaves
out its command replies, and a re-sent post is recorded once. Room posts
raise the unread badge and a room's command replies don't. The device
gains room_login, sharing admin_login's exchange and reading the access
the room granted. Room passwords are remembered in rooms.json; an admin
login is kept in admin.json, shared with Repeater admin. The simulator
has a room, Lakeside BBS, to join with "hello".

Two fixes the research turned up. An admin command took the first direct
message from anyone as its reply, so a room post or a companion's message
landing mid-command was read as the node's answer. It now listens before
sending, and only for a reply from that node that is not a post. And
Repeater admin names a room server's guest password "Room password", the
password members join with.

Refs: #24
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 00:21:34 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 b6e7cd5c16 The handhelds are named for their variant: picocalc-lyra and cardputer-zero
M5Stack makes three Cardputers and only the Zero runs Linux; the PicoCalc
takes several cores and only the Luckfox Lyra runs MeshTerm. So the bare
names were ambiguous, and every reference to the two devices now carries
the variant: the --platform values and MESHTERM_PLATFORM, the Platform
constants (PICOCALC_LYRA, CARDPUTER_ZERO), their lane decks and every
screen's picocalc_lyra_lane / cardputer_zero_lane, the font inventories,
the lane chips' theme styles, the scripts/ folders, the two chrome test
files, and the docs and CLAUDE.md that name them.

There is no alias: --platform picocalc is refused with the three real
choices listed, rather than falling back to the desktop layout. The
PicoCalc is auto-detected from its device tree, so it needs nothing.
What names hardware or a user's data keeps its name: the PicoCalc
keyboard's input-device name, its battery's sysfs supply, and the serial
profile the Lyra setup script writes. .gitignore follows the firmware's
folder, and the changelog keeps the words each release used.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 11:52:41 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 6eb7d8f6bd Each handheld deals its own F-key lane, and the Cardputer Zero joins as a platform
A lane's keyboard-bound half is now a LaneDeck named by Platform.lane_deck: which
keycodes drive the slots, where the chips sit, how they are drawn, and which of a
screen's lane definitions it reads. Screens define picocalc_lane; cardputer_lane
follows it until a screen overrides it, so the two can part ways one screen at a
time. The Cardputer deck is Fn+4..8, Shift with them as F16-F20, eight-cell chips
centred over their keys in the fn key's orange-red and the Shift blue.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 19:33:44 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 79fe79a857 ^R resends the picked message unscoped, not only the newest
With a message picked, ^R (the Resend chip on the PicoCalc) offers that one: a scoped message of ours is resent unscoped, and a pick that isn't one offers nothing rather than reaching past it to another. With nothing picked it is still the newest. The pick clears once the resend lands at the tail, and the picked-row hint names ^R when it would act.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 01:37:20 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 7f59bfa5a0 A channel's region is a bare title atom, and a channel with no scope answers -
The chat title reads "#devtest · onqc" rather than "#devtest · scope onqc". `channels scope N` on a channel with no scope prints - (null in --json) and exits 0: sending under the device default is an answer. Only an empty slot still exits 5.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 23:40:47 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 169c3272c7 The wildcard scope reaches the command, and an empty slot can't be scoped
On Windows, Click expands any argument holding `*` into the files it
matches, quoted or not, so `chat send --scope '*'` arrived as the working
directory's file names. The entry point turns the expansion off; nothing
MeshTerm takes is a glob.

`channels scope N REGION` on an empty slot did nothing and exited 5, which
tells a script the scope was set and read back empty. A write there is
now a usage error; a read still exits 5, as `share` does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 21:37:38 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 7cdc6d9ba3 A channel can be given a region its messages are flooded into
The firmware has no per-channel scope, so a channel send is now a window
on the companion's session scope: set it, send, restore it, under a
transmit lock every flood-capable send also takes (DMs, adverts, admin
commands, logins, regions/neighbour requests), so nothing interleaves.
Ack waits stay outside the lock. A scope the radio can't take stops the
message (FloodScopeError naming the firmware it needs) rather than sending
it unscoped or under the default; * checks the firmware version, since
before 1.16 the override would quietly fall back to the default scope.
A failed restore is repaired by the next transmission.

Channel scopes are set from the channel page's Send scope picker (known
regions with their carriers, or a typed name) and from the CLI (channels
scope, chat send --scope). The chat's title carries the scope as a status
atom, ^R/Resend sends the newest scoped message again unscoped after an
amber confirm, and the scope each sent message went under is recorded
(messages.scope, schema v17) so the paths dialog can say what it went
under and when no known repeater carries that region. Device config now
teaches the region store the default scope it reads or sets.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 21:02:34 -04:00