The activity heading's aside ended "one minute per dot column". The
PicoCalc and the Cardputer draw braille as solid tiles, so there are no
dots there to count, and on 53 columns the words cost the heading its
line. Where braille is solid the aside is "every packet heard" alone;
the axis under the chart still marks the minutes. The desktop keeps it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On the PicoCalc's lane (and the Cardputer's, which deals the same) the
scope chip read "▸ yul" and the Time Machine's window chip "▸ 30 d". The
triangle took two of a chip's six cells to say what its place on the lane
already says, and cut a region's name to four. Both chips are now the
view a press goes to and nothing else, so a name gets all six cells.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On a page taller than the screen, whatever sat above the first selectable
row or below the last could scroll away for good: the frame always pulled
the highlight into view, and nothing up there could take it. Now, with the
highlight on the first row, up scrolls the page a line instead, and on the
last, down does, until the page's real top or bottom is in view. Home and
End take the page to its ends with the highlight.
The highlight may leave the screen meanwhile, on purpose. The snap-back
brings it home: while it is out of view, the other arrow, Enter or Delete
only bring it back, and do nothing else, so nothing runs that cannot be
seen. Any other key ends the edge scroll.
It lives in Screen.edge_scroll, asked before a screen's own handle; select
and reorder lists, the node page, chat, message paths, the path composer,
records and the TX sweep answer cursor_at_edge. CLAUDE.md names the rule.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The channel list's activity sparkline is the row's last column, and
where the row was too wide for the screen, the PicoCalc's above all,
the row's ellipsis cut it off mid-chart. The row now takes the render
width and draws the chart in whatever cells the lanes before it leave,
dropping its oldest buckets so now stays on the right; with no cell
left it is not drawn, and the ACTIVITY heading shortens and then goes
with it, so MSGS is never cut instead.
A row like that declares itself fitted, so the highlight draws it
fitted too rather than as a full line for the arrows to slide. And a
row exactly as wide as its list no longer claims to overflow by a cell.
CLAUDE.md states the rule for any chart that ends a row.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The conversation list now reads CONVERSATION, NEW, LAST, MESSAGE: LAST
over the last message's age, MESSAGE over the message, shortening to MSG
on a narrow terminal. The live feed's timestamp lane is TIME again, and
the channel list keeps NEW.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The chat picker's unread count is headed NEW and its last-message age
WHEN; the channel list's unread count is NEW; the live feed's timestamp
lane is WHEN. Short words over a badge and an age, which UNREAD outgrew
by a cell and had to borrow the gap for. The command line's columns keep
their names: they are a documented contract of their own.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SignPath Foundation's terms make a person approve every signing request,
so the plan's automatic approval doesn't exist. The action gave up after
ten minutes, which would fail a release whenever the maintainer stepped
away; it now waits an hour, and the Windows job's timeout grows to make
room. Only a tag build or a run started by hand asks to be signed: a push
to main that touched the packaging builds unsigned instead of sending an
approval request nobody wanted. Still inert until the SignPath variable
exists.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SignPath Foundation signs a file only when it carries a product name and
version, and their signing configuration checks both. The .exe had neither:
its Properties showed no Details at all. The spec now builds a version
resource from the package's own __version__ (MeshTerm, 0.10.2, the author,
the copyright line), on Windows only. Checked on a local freeze: Windows
reads every field back, and the binary still runs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
SignPath Foundation's application asks for a download page that says the
project uses them for signing, and their terms want a "Code signing policy"
section on it: their credit line, who holds which role, and a privacy
statement. The download note now says Windows signing is on its way instead
of that signing costs money, and the new section says how a build gets
signed, that one person holds all three roles, and what MeshTerm sends
anywhere: map tiles from OpenFreeMap when a map opens, and on the radio only
what you ask it to send (the weekly advert is off by default).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On the PicoCalc the main menu's middle chip, Quit?, opens the quit
confirm, and the confirm's own lane was empty, so leaving took a move
to the Quit button and Enter. The confirm now carries Quit! on that same
F3, sending the same quit, which with the question up is the leaving, as
a second ^Q is. A button dialog can take a lane for that one case; every
other prompt keeps its empty one.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A ── Label ── heading drawn in the border's own colour read as part of
the frame it sits in. Grouped lists' headings and the chat's day
dividers now wear their own theme style, heading: a bold dark grey on
the desktop, slot 8 on the PicoCalc. The frame keeps the accent.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Beside its square the shorter word is unmistakable, and it brings the
whole key to 52 cells, so the legend under a path graph or the contacts
table is one line on the PicoCalc and the Cardputer instead of two. The
type's name stays room server everywhere else, the CLI's answers
included.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Arrowing down the device list on the PicoCalc stalled on every keystroke. The
time was not in the list: at 53 columns the full-size mark does not fit, so each
paint sent load_logo back to the disk for every size until one did, parsed and
measured each, and decoded every row again to centre it. None of that changes
between paints, and the splash paints on every keystroke and every idle tick.
compose_startup now asks _fitted_banner, a one-slot memo keyed by the banner and
the width, dropped on a platform switch with the other composition memos since
the fold bakes the theme in.
Measured on the desktop under the picocalc platform, splash at 53x26, ↓/↑
alternating: 31.1 ms a paint before, 2.3 ms after. The logo was nine-tenths of
the frame; the list itself is under 0.1 ms. 108 frames across 53x26, 53x40,
72x24, 100x30, 40x20 and 53x12, with and without a footnote, picocalc then
regular then picocalc again, come out byte-identical before and after. Not yet
timed on the device, which was unreachable; A/B it with profile-picocalc.
The splash's live rescan (f9e9271) was the first suspect and is not the cost:
a serial poll's device ids are stable, so it repaints only when a device comes
or goes, and the enumeration itself runs in a thread.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
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>
Two shots on 2026-10-01, the login console and MeshTerm's Public channel,
came out upright, unmirrored, and in the right colours (the header's
yellow count and the teal wordmark would trade places with red and blue
swapped), so the untested note goes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 90ae2d56af)
Piped rather than shown in a console, Python on Windows writes stderr in
the legacy code page, so the em dash in "can't reach the PicoCalc" came
out as a replacement character.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 3c050bc44c)
It reads the device's framebuffer, so the picture is exactly what the
panel shows, whatever is on the console, and saves it as a PNG in the
Downloads folder (2x by default, so it stays crisp when shared). The
device half asks the framebuffer driver for its size and colour layout
instead of assuming them; the desktop half needs only Python's standard
library. Connection details come from .dev.env.
Written while the PicoCalc was offline and tested against a simulated
framebuffer; the skill says so until a real run confirms it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit fea0875210)
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>
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>
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>
MeshTerm only ever draws braille as pixels (charts, the map, meters), so
each dot is now a solid tile of its share of the cell: 3x3 at 6x12, 3x2
at 6x8. A run of lit dots reads as one continuous mark instead of a
dotted one. Both fonts take it from the shared generator.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
"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>
Patreon becomes GitHub's built-in `patreon:` entry, so the sidebar shows
patreon.com/MeshTerm with its icon like Buy Me a Coffee, and the Support
page carries the same short URL (a smaller QR code too). PayPal stays the
hosted donate button, only without its scheme in FUNDING.yml: a paypal.me
link would let a donor pay as goods and services and claim a refund for an
item that never arrived, which the donate button's donation label avoids.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A short, plain copy for #announcements, shown last in a code block so it's
easy to copy. JP posts it himself; the skill never does. Also puts back the
notes-repair sentence that had come loose after a code block.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
"Bump minor" meant "a small bump" and nearly shipped 0.11.0. Every release
now starts with a three-way choice showing the number each one produces,
patch recommended, and a major needs a second, separate yes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
Three pull requests against toy issues arrived from agents that farm open
issues for contribution stats. None had said a word to the project, and
none disclosed that AI wrote them.
CONTRIBUTING.md now says, in plain words, how MeshTerm was made and who
it wants: people who use it, AI-assisted or not. Before writing code, a
contributor says hello in the new #contributing channel on Discord and
waits for a yes; the pull request carries their Discord username and a
link to that yes. AI use has to be disclosed in the commits and the pull
request; undisclosed or unattended agent output is closed without review.
The PR template, the README's two notes on pull requests, and the docs
index all said small fixes could skip the conversation, and now match.
The release skill's commit carries the AI trailer its seven release
commits went out without.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Every list of three or more takes the serial comma: the one-liner in
pyproject.toml, meshterm/__init__.py, and --help ("USB, Bluetooth, or TCP",
"Windows, macOS, and Linux"), the README, the changelog, the manuals, the
issue and PR templates, and six strings the app shows (the region-name
rule, a repeater setting's help, the diagnostics tool's description, and
the archive confirms). The Windows-console notice is rewrapped so its
comma keeps it inside 72 columns.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>