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>
Four different one-liners were in circulation: the repository's "interactive, scriptable
CLI toolkit", the README's "full-screen terminal companion", the About page's "client that
lives in your terminal", and the package summary's "TUI-based client ... companion
connection". JP picked one:
A full-featured TUI MeshCore client for your terminal
It now opens the README, the About page, the package summary, meshterm.__doc__ and the
`meshterm --help` header, which cli.py already says must stay the same as the summary.
The summary's second sentence loses its grammar slip on the way: "Connects to a companion
over USB, Bluetooth or TCP." CLAUDE.md opens with the same line.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The first public release. The version goes to 0.9.0 and the changelog carries one
entry, which the release job lifts verbatim as the release notes.
[Unreleased] had refilled since the last fold with one change -- --mock keeping its
invented mesh out of your real history. On a first release there is no previous
behaviour for it to have changed from, so it is folded into the entry's existing --mock
sentence, in that entry's voice, and [Unreleased] is empty again.
Reading the notes as a newcomer would, three claims were not true, and each was checked
against the running app rather than argued:
- `meshterm map` is not a command. It was the first example a stranger would type, and
it answers "No such command". The map is a picture, so it lives in the menu; the
example is `meshterm info` now.
- The Time Machine does not wind the map back. It is braille charts of recorded history
over a day, a week, a month or all of it -- no map in it at all.
- Only the PicoCalc gets the handheld layout. It is chosen solely by the device tree
reading "Luckfox Lyra"; the uConsole has no detection and runs the desktop layout on
its 1280x720 screen, which has the room for it.
The manual's three sample outputs move from 0.3.7 to 0.9.0 with it, so the version a
reader sees in docs/cli.md matches the one they downloaded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The macOS installers of 0.3.3 never built: the notices step refused pyobjc-core and
pyobjc-framework-libdispatch, whose wheels ship no licence text. Their MIT text is vendored
now, and a test checks every vendored path on every platform.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Apache-2.0 does not require per-file headers, but a file lifted out of the tree carried no marking at all. One comment line, first (or right after a shebang), on every .py under meshterm/, tests/, packaging/ and scripts/, plus the extensionless SPI bridge; tests/test_spdx.py keeps a new file from landing without one.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The command line was rebuilt around the fact that it has two readers.
`--json` is the machine contract and now covers all 49 subcommands
instead of two; the plain face is free to be read by a person, which is
what it was always for.
The changelog entry had been written for the intermediate design — it
promised quoted names and absolute timestamps, both of which the finished
work deliberately reverses — so it describes what shipped instead.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
JP saw the PicoCalc's wordmark on a desktop session. The splash picks the
widest mark that fits, the desktop one is 71 columns, and below that it swaps
to the 53 drawn for the handheld -- so the app was announcing itself as the
handheld and truncating every menu description onto the pager underneath.
The window was 64 columns. reopen() passed no window argument, so wt.exe put
the session in a *tab* of whatever window happened to be open and it inherited
that size; with no window open it would instead have taken the reader's global
launch size, which is a setting about their shell and nothing to do with this
app. Either way the app could land somewhere too small for it, and the failure
was quiet and cosmetic rather than anything that announced itself.
It now asks for a window of its own at the platform's stated minimum plus a
margin -- 80x30 on the desktop, derived from readable_cols/readable_rows so it
follows them. Windows Terminal clamps to what the display can show, so a small
screen degrades exactly as it did before.
The tab is titled MeshTerm too. It was showing the executable's full path,
which on a downloaded build is a line of Downloads folder. The icon is not ours
to set -- Windows Terminal takes that from a profile and a bare command line is
not one.
Release 0.2.8.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The classic Windows console cannot draw a single MeshTerm icon whatever font
it is given, so "reopen in Windows Terminal?" had exactly one sensible answer
-- and was being put to someone who had not seen the app yet and so had nothing
to weigh it against, at the one moment nothing had been drawn.
So it moves. It still says what it is doing and still names the way to stop it,
because a program that opens a window you did not ask for should say so.
Installing a font keeps its consent, and the split is deliberate: moving window
costs nothing and undoes itself by closing, while a font is a file written to
someone's machine that Windows will not let go of until their next logon.
The console_setup preference reads auto/off now rather than ask/off, and its
help says what it does. An older stored "ask" is not "off", so it keeps working
and simply means auto.
JP's call, and it is the right one -- the reference experience should be what
an end user gets, not what they think to ask for.
Release 0.2.7.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found by testing the release artifact rather than the repo. A PyInstaller
one-file build unpacks itself and re-runs itself, the halves coordinating
through _PYI* environment variables. reopen() handed the whole environment to
the new copy, so its bootloader concluded it was stage two of a launch nobody
had made, checked that its parent was the same executable, found wt.exe, and
aborted:
[PYI-33368:ERROR] Security validation failure: parent process has
different executable!
[process exited with code 4294967295]
A new window holding a message nobody can act on, with the window that offered
the move already gone. Strictly worse than not offering.
child_environment() strips them, and restores the loader paths the bootloader
parks aside. Verified against a locally frozen build: before, the bootloader
error; after, the session comes up in Windows Terminal with its emoji and its
charts.
The source build could never have shown this. Same shape as 0.2.2's empty tool
registry -- the packaged build is a platform of its own, and the only way to
test it is to run it.
Release 0.2.6.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MeshTerm died building its screen on Windows with a ctypes type error, and on a
double-clicked build the traceback and the window it printed to were destroyed
together — which is why this needed a log file before it could be looked at.
`ctypes.windll.kernel32` is a cache. Every caller in the process receives the same
object and each function on it is a shared attribute, so declaring `.argtypes` is
not configuration, it is a process-wide mutation where the last writer wins. The
emoji-width probe declared `GetConsoleScreenBufferInfo` as taking a pointer to its
own copy of the screen-buffer struct; prompt_toolkit then called that same function
object with a pointer to its own copy — same layout, different class — and ctypes
refused it.
Nothing here reaches for `ctypes.windll` any more. Five call sites take private
handles from `core/win32dll.py`, built by constructing `WinDLL` directly so each
one gets its own function cache and a declaration made for one purpose cannot be
seen by anything else. Each call returns a fresh handle, because two parts of
MeshTerm disagreeing about a signature is the same bug in miniature.
The regression test was checked against the defect rather than assumed to cover
it: with the old line restored it fails, and with the fix it passes. A test that
passes either way would only have recorded that somebody thought about the
problem.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The build to diagnose a double-click with: a window that waits after a failure, a
log that keeps what happened, and a level you can turn up before reproducing it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The first build that contains the whole app. 0.2.1's binaries start and then
offer you nothing, so this replaces them rather than adding to them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes out the changelog's Unreleased section under a date, which is the whole
ceremony: everything since 0.2.0 was written down as it landed, so cutting a
release is renaming a heading rather than reading a week of commits back.
0.2.0's link points at its tag rather than a release page, because 0.2.0 was
never released anywhere — it was tagged locally before there was anything to
build or anywhere to put it. 0.2.1 is the first version that exists outside this
machine.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two pieces of release machinery that have to exist before a first bump, plus
the bump: 0.1.0 to 0.2.0.
The version was written twice — `pyproject.toml` and `meshterm/__init__.py` —
and kept in step by hand. That is a drift waiting to happen, and an unusually
visible one here: `__version__` is read by the splash wordmark, the menu mark,
the About page, a trace export and the tile-server user agent, so the number a
user reads on screen is the package attribute, while the number in a built
wheel would have been the `pyproject` literal. `[project] version` is now
`dynamic` and hatchling reads the attribute, so there is one place to edit and
the two cannot disagree. Verified against hatchling rather than assumed: it
resolves `0.2.0`.
`CHANGELOG.md` starts in Keep a Changelog form, with the `[Unreleased]` heading
that makes release notes a rename rather than an excavation of the log. Its
first entry is 0.2.0 and it says plainly that the history before it is not
reconstructed — 596 commits of pre-history would be a worse document, not a
more complete one.
0.2.0 rather than something nearer the announced 0.9.0: the numbers between here
and the release are for real feature sets, and there is no reason to spend them
early. 0.9.0 is a number chosen for the launch, not one to be arrived at by
counting — landing on 0.7.0 on the day and bumping straight to 0.9.0 is fine.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two words the project was saying two ways, now settled to one each.
`macOS` is how Apple has styled it since 2016, and the repo was already split:
`connection.py`, `flash.py` and `test_disconnect.py` had it right, while the
About page and the new Summary said `MacOS`. Three files change, all of them
prose. Same five characters either way, so nothing about a rendered width moves.
`BLE` becomes `Bluetooth` in the Summary and the package docstring. Those two
lines are read by people deciding whether this runs on their machine, and
`Bluetooth` is the word they know and the word the About page already uses. The
identifier stays `BLE` everywhere it is one — the transport name, the connection
code, the config keys, the CLI. That is the boundary: the acronym is what the
thing is called in the code, the plain word is what it is called when introducing
the project to someone who has not met it yet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Summary is the only description most people will ever read: it is the line
under the name in PyPI search results, the one a package manager echoes, and the
sentence that decides whether anyone opens the README. It said "A modern
command-line toolkit for tuning and exploring a MeshCore mesh", which named a
category rather than the thing, and left out every fact a reader is actually
deciding on.
The new one is JP's own wording and answers those instead: what it is (a
full-featured TUI-based MeshCore client for your terminal), how it reaches a
radio (USB, BLE, TCP), and whether it runs on the reader's machine (Windows,
MacOS, Linux). "USB" and "MacOS" are the spellings the About page already uses,
so the front door and the metadata now say it the same way.
The package docstring carries the same sentence, split at the summary line as
`D205` wants, so `help(meshterm)` and the PyPI page agree.
The README's opening is untouched. It is doing a different job — a paragraph that
gets to breathe, with the links and the longitudinal-record pitch in it — and a
one-line summary is not a replacement for it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`E501` across 69 lines, wrapped rather than exempted.
The prose ones — module docstrings in `menu`, `node_detail_screen`,
`pathline`, `pathgraph`, `walk_screen`, `trace_runner` and `test_about` — are
reflowed as paragraphs, so the block keeps its shape instead of gaining one
short orphan line. Nothing they say changed.
The code ones mostly gained a line break inside a call. Where a break would
have read worse than a name, the sub-expression got one instead: `reach` in
`topology`'s quality clamp, `plain_cells` in `pathline`, `numbering` in the
trace list, `listed` in `flash.py`'s refusal, and a small `shape`/`is_strip`
helper in two tests that were comparing or matching the same shape twice on one
line. Four docstring summaries lost a word they were not using.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Consolidate the copyright into a single meshterm.copyright_notice() beside
__version__ (was defined twice, disagreeing on wording, holder, and year
span). Every startup splash draws from it, so it can never drift.
Place it immediately under the wordmark, right-justified to the logo edge, so
the two read as one signed block. Simplify the display logic: it now rides
every splash that shows the logo (picker, spinner, PIN, notices) rather than
being dropped after the first selection.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rename the package directory, imports, brand strings, env-var prefixes,
console-script entry point, and the config/data dir (~/.config/meshterm).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>