Commit Graph
654 Commits
Author SHA1 Message Date
Jean-Pierre Martineau a2e2500efe Merge branch 'main' into cardputer-zero
# Conflicts:
#	tests/test_chat.py
2026-10-02 00:58:32 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 810232cfb6 A link written without a scheme is found too, by www. or a real TLD
meshterm.net/map and www.example.org are links now, as surely as their
https forms: a host starting www., or ending in a top-level domain IANA
has delegated, read from the registry itself, kept verbatim in
assets/tlds.txt. So file.txt stays text, txt being no TLD, and so do a
decimal, an abbreviation, an address, and a path. A scheme-less link
opens, and its QR code encodes, as https://. Link finding moves out of
the chat screen into core/links.py.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 00:55:59 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 cd68498feb On the Cardputer, a link's code is on ^U, as on the desktop
Its 14 rows can't spare a code under every message that carries a link,
so it keeps url_codes off and reaches a link's code the desktop's way,
on the share screen. Its braille code is small enough that a link and its
code share the panel. A test pins all three: no code under the message,
QR on the lane's Shift bank, and the share screen inside 53x14.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 00:18:33 -04:00
Jean-Pierre Martineau 8755adc6f8 Merge branch 'main' into cardputer-zero
# Conflicts:
#	CLAUDE.md
#	meshterm/ui/chat.py
#	meshterm/ui/qr.py
#	meshterm/ui/walk_screen.py
#	tests/test_chat.py
#	tests/test_gallery.py
2026-10-02 00:15:20 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 c6c80b4c1f A chat link is never cut where the screen can hold it, and ^U shows its code
A URL too long for a message's hanging lane steps out to the timestamp's
column on a line of its own, whole, since a terminal opens a link, and a
reader copies one, only while it sits on one line. Only a URL wider than
the screen still folds. ^U on a picked message opens its links on the
share screen, one code at a time, and with several, the arrows step
through them, the URL line flanked by a lit or dim arrow each way. On the
PicoCalc the lane carries it as QR, behind Day down.

CLAUDE.md also states the shortcut rule: a bare letter only on a screen
that takes no typing, a Ctrl chord everywhere letters are text.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 23:07:51 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 f847ebd637 Locating our own node is ^Y, not ^U
Y for you, on the map and in the mesh walk alike.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 23:05:06 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 71edf6a937 The map runs right up to the panel's side borders
The bordered frame pads its body one column in from each side border,
which is air for a row of text and two columns of drawing given up on
the map. A screen can now be flush (Screen.flush), and the map is: its
body is cols - 2 wide on the desktop, base_body_size agrees through the
shared panel_inset, and the braille edge scrub drops to the one border
column. The borderless platforms already drew the full width.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 23:03:01 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 3c75374379 The map runs right up to the panel's side borders
The bordered frame pads its body one column in from each side border,
which is air for a row of text and two columns of drawing given up on
the map. A screen can now be flush (Screen.flush), and the map is: its
body is cols - 2 wide on the desktop, base_body_size agrees through the
shared panel_inset, and the braille edge scrub drops to the one border
column. The borderless platforms already drew the full width.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 22:46:39 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 e66a5619dc Locating our own node is ^Y, not ^U
Y for you, on the map and in the mesh walk alike.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 22:10:19 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 9b215172d0 On the PicoCalc, a chat message's links hang as QR codes under it
Every URL in a channel or direct message, any scheme (a meshcore://
share as surely as a web link), now gets a braille code under the
message, hung at the body's indent. Several sit side by side and wrap
onto a new band when the line is full, so a link read on the handheld
opens on a phone instead of being typed out. Platform.url_codes keeps
it to the PicoCalc: half-block codes would swamp a desktop transcript,
and the Cardputer's 14 rows can't spare 9 under every link.

A picked message now keeps its whole block in view, codes included,
rather than only its first row, so walking the pick down onto a link
no longer leaves its code below the fold.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 00:37:33 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 ec850d8e93 On the PicoCalc, a chat message's links hang as QR codes under it
Every URL in a channel or direct message, any scheme (a meshcore://
share as surely as a web link), now gets a braille code under the
message, hung at the body's indent. Several sit side by side and wrap
onto a new band when the line is full, so a link read on the handheld
opens on a phone instead of being typed out. Platform.url_codes keeps
it to the PicoCalc: half-block codes would swamp a desktop transcript,
and the Cardputer's 14 rows can't spare 9 under every link.

A picked message now keeps its whole block in view, codes included,
rather than only its first row, so walking the pick down onto a link
no longer leaves its code below the fold.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 00:36:50 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 28bd59cf1b The Cardputer draws braille as solid tiles, and its QR codes in braille
The console host now draws every braille cell over Terminus's dotted
ones as solid 3x3 tiles, the same tiles as the PicoCalc's font build,
so the map and the charts draw unbroken lines and Platform.solid_braille
holds here too. Every QR code is then braille, one module a dot: the
share screen fits the 14-row frame instead of being cut, a channel
share with its link under it, a contact card with its link a page down.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 00:26:55 -04:00
Jean-Pierre Martineau e31d24e12b Merge branch 'main' into cardputer-zero 2026-10-01 00:22:54 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 6840138eb4 On the PicoCalc, every QR code is braille, one module a dot
Braille was a fallback for codes too big for half blocks; on the
PicoCalc it is now the grid every code is drawn on, eight modules to a
cell, share screens and page fences alike. A channel share is 23 cells
by 12 rows there, a contact card 29 by 15, each with its link under it.
The regular platform keeps half blocks, its fonts' braille being dotted.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 22:55:01 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 edacbd0740 On the PicoCalc, a QR code too big for half blocks steps down to braille
Now that the PicoCalc's fonts draw braille as solid tiles, a braille
cell is a true 2x4 pixel grid, so it can hold a QR code at half the
module size. The fit ladder tries every half-block fit first (a lighter
error level and a narrower quiet zone cost a camera nothing on a
screen), then the same fits in braille. That puts the contact card on
the 53x26 panel whole: the standard code in 29 cells by 15 rows, with
its link under it, where half blocks had to send the link a page down.

Platform.solid_braille says where braille is solid; desktop fonts draw
it dotted, so the regular platform never offers it. Codes are cached
per (data, error level) since the share screen refits every paint.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 22:32:43 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 a62bf9ffdb The Cardputer's lane chips are laid out as the PicoCalc's
Key caption, nine cells a chip, two between, at the same columns (JP); only the
captions, F4-F8 in both banks, and the colours are the Cardputer's own.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 21:34:13 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 a5b53122ad Two message-paths tests record a viewport before painting, as the frame does
Unset, the viewport reads as one row, which is now a short frame that leaves the
graph's caption out, and the caption is what these two check.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 21:17:41 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 390a0a1616 On a short frame, a graph's caption and node-type key step aside
Message paths and the mesh walk keep their list's rows on the Cardputer instead of
spending three on a key to marks that mean the same thing everywhere; the rule is
widgets.short_frame, the same one that draws a tab strip on one row. The message
paths budget also counts the key's real height, which wraps to two lines at 53
columns and had been counted as one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 21:14:55 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 5675ba0e1f Tabbed pages draw their strip on one row when the frame is short
Below sixteen viewport rows the boxed strip's three rows become one, the active
tab standing on the rule between tees and every label held where it was. On the
Cardputer that puts the node page's actions and a record's whole Info list back
on screen. The node page's location preview steps aside when it can't have its
five-row floor, rather than scrolling the header and actions away.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 21:13:03 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 b3bf5a6160 The Cardputer Zero gets a console host, and a desktop simulator built on it
The stock image gives an app no text console, so MeshTerm draws its own: the TUI
runs in-process with prompt_toolkit's output feeding a small VT parser, the cells
are drawn in Terminus 6x12 with MeshTerm's marks over it, and keys go in through a
pipe in xterm's spelling. The front end is the only part that differs: framebuffer
and evdev on the device (written ahead of the hardware), a Tk window on the desktop.

Each handheld's glyph inventory is now named by Platform.font, which replaces
ascii_fold; the render-boundary fold derives its accents, emoji, and fallback from
that inventory and quantizes colour only where truecolor is off.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 21:08:30 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 64fce69de7 The gallery renders every screen at the Cardputer Zero's 53x14 too
Same hard width gate as the PicoCalc, the lane checked through the platform's own
deck, and no emoji in a handheld's output. Rows stay ungated, since a body scrolls;
the screens that are cramped at 14 rows are a judged worklist instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 20:43:15 -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 c1d0772baf Lint and format the last few commits
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 02:09:04 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 2d6b8ada6e The main menu's quit hint reads ^Q quit?, since it asks first
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 01:52:49 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 dd64ea8e87 The PicoCalc's main menu shows its way out, with Quit? on F3
With Esc inert at the main menu, the PicoCalc had nowhere to say how to
leave. Its title bar now ends on ^Q quit there (Esc clear while a filter is
typed), and the menu's F-key lane puts Quit? on F3, which asks first, with
Quit! behind it on F8 to leave without asking. Follows up issue #22.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 01:46:06 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 7071214660 ^Q asks before it quits, and Esc at the main menu stays put
Esc-mashing back to the menu used to land on the quit confirm, and ^Q, one
key from ^W, quit with no question at all. Now Esc at the main menu only
clears a typed filter, and ^Q floats the same quit confirm the Quit row
opens, from anywhere and without stopping what runs underneath; a second
^Q while it asks leaves at once. The main menu's hint names ^Q quit, the
only place the chord is shown. Option D from issue #22.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 01:31:03 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 0e9f41fb2a A reboot no longer waits for an acknowledgement the board never sends
Over Bluetooth every command is a write-with-response, and a companion that
restarts on the reboot command can drop the link before acknowledging the
write. The write then hung until the link supervision timeout, holding the
reboot dialog back by a second or more. The reboot now returns once the
write has had a quarter second to fail, and leaves the rest to the teardown
that follows.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 01:20:05 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 cdea534fa0 A reboot sent from Device config raises the reconnect dialog at once
The reboot flow used to send the command and then wait for a liveness poll
to see the link drop. A quick reboot behind a port that never goes away
(a USB-UART bridge) was never seen, so the app went on sending commands to
a board mid-restart. Sending the reboot now declares the link down itself:
the session watcher wakes on the announcement, the Device rebooting dialog
takes over, and the first reconnect attempt waits a moment for the board to
actually begin restarting.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 01:09:32 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 3f73716ff5 The Channels list leads with each channel's slot number
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 23:38:23 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 854b7fd765 A T-Deck on MeshOS or wadamesh pairs over Bluetooth like any companion
Those firmwares guard only the UART write characteristic, so the
refusal arrives on the handshake's first write, which meshcore's send
swallows. The empty handshake read as "not a MeshCore companion", and
the PIN pairing, the stale-bond repair and the macOS Passkey wait never
ran. The write is now wrapped to keep its refusal, and an empty
handshake behind one raises it, so both firmware shapes take the same
pairing path.

bleak no longer gets the PIN on Windows either: its WinRT pair() is
Just Works only, and after a mistyped PIN it left Windows holding an
unauthenticated bond that the next, correct attempt reused and was
refused over.

Verified against a T-Deck on MeshOS 1.3.0: a planted Just Works bond
heals in one run, and an unbonded connect without a PIN now says the
device needs one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 23:26:14 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 307e767570 The Time Machine's whole-mesh page cycles through each scope, as the dashboard does
s (F2 on the PicoCalc) steps all → unscoped → each region heard in the window
A–Z → unknown scope, beside w's window cycle. A narrowed page charts that
scope's floods per day (per hour at 24 h), their time-of-day rhythm, and its
own ledger; nodes per day and arrivals leave, being read off adverts, which
carry no scope. A window's stored floods are read once (Repository.flood_frames)
and resolved against the known region names for the page's life.

The node and own-node pages offer no cycle: a node's page is its adverts and
telemetry, and what we send is traces, which go direct.

The ring's order, words and chip move to ui/scopering.py, shared with the
dashboard.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 02:35:31 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 247c9bf8c7 Splash letters said white after a reset and drew grey
An art editor spells white as a reset then a bare bold (ESC[0m ESC[1m): the
console's default grey, brightened. The loader forgot the colour on a reset,
so it dropped the bold with nothing restated and the run fell to the
terminal's own default grey. After a reset the grey is now the colour in
force, and a bold lifts it to bright white like any other.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 02:20:36 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 1596f121a3 The dashboard cycles through each scope heard, as the Time Machine does windows
s (F3 on the PicoCalc) steps all → unscoped → each region A–Z → unknown
scope → all, and the whole screen follows: the activity chart, the pulse
rate, the traffic tallies and the SNR/RSSI medians narrow to that scope's
floods, while the radio's own numbers stay whole. A narrowed view reads the
screen's 2-hour window, since naming a frame's region is a per-frame check
the database can't run; the headings say so, and a direct frame, which has
no scope, counts only under all. The title names the view, falling back to
"Dashboard · scope …" where a long region name won't fit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 00:28:40 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 97d4d231c7 The dashboard's traffic counts every frame once, under the live feed's names
Traffic listed what the device reported (advert, telemetry, message, ack)
beside the frames those reports arrived in, so one advert was counted twice
and its frame row had to be renamed "raw advert" to tell the two apart. It
now counts frames heard on the air only, named by the same function as the
live feed's class lane (payload_marks), and every bar takes the colour the
feed draws a frame's class in.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 00:22:51 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 3fccfd7971 The Contacts title counts the archived too, abbreviating where it's narrow
"Contacts · 198 known + 123 archived", falling back to "Contacts · 198 + 123"
where the frame's rule can't hold the words. The fallback is a new
Screen.short_title the frame picks through fitted_title, on both the bordered
panel and the PicoCalc title bar.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 23:41:53 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 0cd40bbd6c Fix three small CLI rough edges: records --width, the simulator's label, two help pages
records --width now refuses anything but 1, 2, or 4 with a usage error, the
way --category already refused an unknown discipline. A width that doesn't
exist used to come back as exit 5, indistinguishable from an empty database,
and --width 0 was falsy and so listed every width. (#15)

A device with no port is labelled by its name alone. The simulator fell
through to the serial branch and came out as "… (nothing transmits) ()". (#14)

platform and specimen pass their own one-line help, so Typer stops printing
their docstrings — notes for whoever reads cli.py, Sphinx roles and source
paths included — and their summaries fit in meshterm --help. A new test runs
--help on every registered command and refuses those markers anywhere. (#16)

Built from the issues alone; the pull requests filed against them were
closed unreviewed and their code was never read.

Closes #14, closes #15, closes #16.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 10:59:17 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 79137598cf Clock sync explains a radio whose clock is ahead instead of calling it malformed
MeshCore firmware sets its clock only forward: CMD_SET_DEVICE_TIME with an earlier time is answered ERR_CODE_ILLEGAL_ARG, which read as 'the device rejected the request as malformed'. Since 2026-09-24 the companion's clock has been ahead of this computer's (itself within 0.7 s of NTP), so every connect's sync was refused. set_clock now reads the radio's clock first: within a minute ahead is in sync and nothing is written; further ahead raises ClockAheadError, which says by how much and that a reboot resets it. The device layer also maps the firmware's refusal to that error, and the simulator's clock now only moves forward, like the firmware's.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 17:10:09 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 bd0c311dbc Bluetooth on Linux stops stranding its own link, and pairs with the PIN first time
Measured against a T1000-E from a uConsole (BlueZ 5.66, btmon on the side).

A connect that BlueZ had to retry stranded its link. bleak retries a link
attempt that failed to synchronise (le-connection-abort-by-local, 3-7 per
connect on a Pi radio) inside connect(), and each one fired meshcore's
disconnect handler, which resets BLEConnection.client to None. bleak's
next retry then connected a client nobody held, meshcore gave up as "not
established", and the live link kept the companion from advertising, so
the retry scanned for 30 s and reported it out of range. Three runs out of
three. While connect() runs, a disconnect is now only recorded, and the
teardown keeps the last live client if meshcore let go of it anyway.

A companion given a PIN after it was paired unauthenticated answers the
subscribe with ATT 0x0E, Unlikely Error (bond Authenticated=0). That is now
read as a pairing refusal, so the stale-bond message and the PIN repair
reach it; with the PIN it re-paired and connected.

BlueZ's Pair left its link up (the companion then stopped advertising to
the connect), and failed at once on the same link flake bleak retries. It
now puts the link down after pairing and retries a flake a bounded 4 times.

And BlueZ starts a pairing of its own when the subscribe is refused, asking
the default agent; with none (any headless machine) it waited on a yes/no
until the companion's 30 s SMP timeout. For the length of a connect
MeshTerm is now the default agent for that one device, KeyboardOnly: with a
PIN it answers, without one it refuses at once, and it never approves a
pairing without the passkey, which is how a bond ends up weak.

Unpaired, no PIN: 33 s -> 4 s to "requires a PIN". Unpaired with the PIN:
paired and connected in 11 s. Paired: connects in 7 s, where it failed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 04:37:51 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 bd222105ae Two tests stop racing CI: the chat waits for its send, the BlueZ fixture leaves os alone
The BlueZ fixture patched os.getpid to return a string, and bluez.os is the
global os module, so Python 3.14's event loop (which opens a pidfd for its own
pid at startup) failed before any test ran. The agent path is now derived from
the real pid instead.

The end-to-end chat test piped Esc beside Enter, so a slow runner left the
screen and cancelled the spawned send before the row was written. It now sends
Esc only once the message is recorded.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 04:16:26 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 491325f995 Linux pairs a Bluetooth companion with its PIN, and a serial port says why it won't open
On Linux the PIN never reached the radio: bleak's BlueZ pair() calls
Device1.Pair and nothing else, BlueZ asks a pairing agent for the passkey,
and bleak registers none. Without a desktop agent BlueZ falls back to Just
Works, the firmware refuses a bond without MITM, and the picker looped on
"That PIN was rejected" for a correct PIN. MeshTerm now registers its own
Agent1 for the length of one pairing (core/bluez.py) and calls Pair from
the same bus connection, so BlueZ routes the passkey request to it and
never to the desktop's agent. The Windows repair contract carries over:
an existing bond is reused, force removes it and pairs afresh, and the
outcome is recorded so a refusal names it. Unpair & quit and the stale-
bond message work on Linux too, and the remedies there name Linux and
`bluetoothctl remove` instead of Windows' Settings. bleak no longer gets
the PIN on Linux, where it could only start a second pairing.

A serial port that refuses to open was reported as "didn't answer as a
MeshCore device", with nothing logged. Permission denied now names the
dialout group, a held port names the other program (and ModemManager on
Linux, also named when the port opens but never answers), and a vanished
port says so. A failed startup probe is logged at warning.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 03:58:26 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 ed2ad3b7ab A Bluetooth connect that fails says which step failed
One "requires a PIN" sentence covered every GATT refusal, and a link that
never opened read as "didn't answer as a MeshCore device". A Heltec V4 user
whose Windows bond had gone stale was told to pair a device Settings showed
as paired. Each failure point now has its own message and remedy: link never
opened, stale Windows bond, wrong PIN, device refused to pair (with the
WinRT status), paired but still refused, a BLE probe timeout by stage, and
link up but no identity reply. The PIN dialog shows the diagnosis instead of
calling every refusal a rejected PIN, and each case is logged at warning so
a log sent in from another machine names it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 03:43:15 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 40183e89ca The live feed's SNR and RSSI labels start where their lanes do
Both labels were right-aligned over their numbers, so each ended
mid-value with the unit trailing past it; every other label on the row
starts at its lane's left edge. RSSI also borrowed the SNR's five-cell
field for a reading never wider than four (-120), which set the two
readings three or four cells apart instead of the row's usual two.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 03:40:33 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 6b694e3bd2 Packet classes take shorter names, and the class column narrows to 9
dir messg, chan text, chan data and anon req replace direct message, channel text, channel data and anon request, so the longest class (telemetry, chan text, multipart) is 9 cells and the live feed's class lane gives 5 of its 14 back to the row. The dashboard's traffic tallies and the viewer's headline read the same names from the same table.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 03:07:11 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 d019c840a8 The live feed never collapses a packet's class name, and the PicoCalc drops its icon
The class name was dropped below the width the whole row needed, leaving only its icon. It is now drawn at every width, like the scope; a row wider than the screen is read by scrolling the highlighted one with ←→. The class icon beside the name is a family cue once the name is always there, so it follows Platform.menu_icons: on the PicoCalc, where it could only be a one-glyph stand-in, the lane is gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 02:38:41 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 01a8f711d6 The session flood scope is framed by MeshTerm, so an older meshcore 2.3 can send scoped
reset_flood_scope and force_unscoped arrived late in meshcore 2.3.x. The uConsole's 2.3.7 satisfies meshcore>=2.3 without them, so every scoped channel send would have crashed on restoring the scope. set_flood_scope now sends the firmware's command 54 itself ([54][0][key], [54][0], [54][1]), as the default scope's command 63 already was. Every other library call MeshTerm makes exists in 2.3.7.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 02:06:44 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 b0d89a5ece A received channel message's card names the region it was flooded into
The radio hands a message over as text, not as the frame it rode in, so a 💬 row opened from the live feed had no scope to show. Its card now finds the message's flooded copies in the packet log, the way the message paths dialog does, and reads the scope off them into a scope row. The copies found are kept per row; the region is resolved on every paint, so a name learned while the card is open names it. Direct messages get no row: their copies are encrypted end to end, and a routed one has no scope.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 01:42:45 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 b89cd266cc The region editor says when a repeater has no home region
The firmware parks its home mark on the wildcard when no region is home, so *^ means no home region set. The wildcard row's note now reads 'unscoped · no home' then, and 'unscoped floods' once a region is home.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 01:38:00 -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 bdf166ce6d The live feed's SCOPE column never collapses
It came and went with the terminal's width: hidden between 70 and 83 columns, where the class label won, and always on the PicoCalc. It is now drawn at every width, and the class label is what gives way on a narrow screen, the icon still saying what the packet is. Beside the subject, the lane is on screen even at 53 columns; there it is the readings after it that scroll off.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 00:24:49 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 fcd70c2e2e The packet viewer states a flood's scope on a row of its own
The region rode the route row as a trailing atom (flood · scope harbour), easy to miss on a card opened from the feed's SCOPE lane to see it. Route now says how the packet was routed (flood, direct) and a labelled scope row underneath says where: the region, 'unknown region · code 3fa1', or 'unscoped'. A direct packet has no scope row.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 00:14:51 -04:00