6 Commits
Author SHA1 Message Date
Jean-Pierre MartineauandClaude Opus 5.5 a9e650c07d MeshTerm 0.10.1
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>
2026-09-27 11:05:41 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 b09d97f8a3 The release skill always asks which bump, and asks twice for a major
"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>
2026-09-27 11:05:41 -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 1949d5b1a3 The uConsole guide says the bridge move-over is for setups from 0.9.0 and earlier
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 09:47:16 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 700cbb57b7 Contributing: say hello on Discord first, disclose AI use, and no bots
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>
2026-09-27 09:45:20 -04:00
Jean-Pierre Martineau f02b794861 The release skill never recommends more than a patch bump 2026-09-25 22:16:41 -04:00
14 changed files with 237 additions and 30 deletions

No files matched your search

+28 -3
View File
@@ -48,8 +48,27 @@ A release does not go out on a red suite. If something fails, stop and say so.
## 2. Decide the version
Patch unless JP says otherwise. Say the number you arrived at before you write it anywhere,
so a wrong assumption costs a sentence instead of a tag.
**Always ask, as a multiple choice**, whenever JP asks for a release — even when his
request names a bump, because words like "minor" have meant "a small one" before (he
asked to "bump minor" and meant the patch). One AskUserQuestion, three choices, each
showing the actual number it produces:
- **Patch — X.Y.(Z+1)** (Recommended)
- **Minor — X.(Y+1).0**
- **Major — (X+1).0.0**
**Major asks twice.** If he picks it, ask a second, separate question before writing the
number anywhere — "Release (X+1).0.0? A major version is a promise about compatibility
that can't be taken back." — with *Yes, (X+1).0.0* and *No, go back to patch* as its
choices. Only a second yes makes it major.
Then say the number you arrived at before you write it anywhere, so a wrong assumption
costs a sentence instead of a tag.
**Don't recommend a bigger bump**, however many features or changed defaults the release
carries — not even on the strength of the changelog's SemVer note. 0.10.0 went out that way
on a recommendation, and JP would have preferred 0.9.1. A minor or major bump happens only
when he names it.
## 3. Write the entry
@@ -116,10 +135,16 @@ field, which is the live `{version}` placeholder and must show the new one.
## 5. Commit, tag, push
```
git commit -am "MeshTerm X.Y.Z"
git commit -a -m "MeshTerm X.Y.Z" -m "Co-Authored-By: <the model running this skill> <noreply@anthropic.com>"
git tag -a vX.Y.Z -m "MeshTerm X.Y.Z"
```
The trailer is not optional: the changelog entry in this commit is prose you wrote, and
CONTRIBUTING.md asks every contributor to say when an AI tool wrote part of a change —
the project's own commits say it first. Name the model actually running, not a
hardcoded one. Seven release commits (0.3.1 through 0.10.0) went out without it because
this line used to omit it.
Commit on `main` — never a topic branch. Then **confirm with JP before pushing**, because
the push is the act that builds and publishes the release:
+16 -4
View File
@@ -2,15 +2,27 @@
<!-- The why matters more than the what — the diff already says the what. -->
## Related issue
## Discord
<!-- Small fixes can come straight to a PR. For anything bigger, CONTRIBUTING asks for an
issue first and a yes on it, so a large PR with no issue behind it will get a
conversation before it gets a review. Link it here. -->
<!-- Your Discord username on the MeshTerm server, and a link to the message in
#contributing where you got a yes. A pull request without them is closed without
a review. -->
Username:
Link to the yes:
## AI use
<!-- Required. Did an AI tool write any part of this change? If yes: which tool, what it
did, and what you checked yourself. Each commit it helped with also needs a line at
the end naming it, like `Co-Authored-By:` or `Assisted-by:`. If you used AI and don't
say so, this pull request is treated as a bot's and closed. -->
## Before you mark it ready
- [ ] I've read [CONTRIBUTING.md](../blob/main/CONTRIBUTING.md) and agree its licensing terms apply to this contribution
- [ ] I've put my Discord username and a link to the yes above
- [ ] If I used AI, I said so above and in the commits it helped with
- [ ] `python -m pytest -q` passes, including the dual-platform gallery gate
- [ ] `ruff check .` and `ruff format --check .` are clean
- [ ] New screens, rows, and hints go through the helpers in `ui/menus.py`, and follow the
+20 -1
View File
@@ -9,6 +9,24 @@ one caveat SemVer makes for a leading zero: while the major version is still `0`
## [Unreleased]
## [0.10.1] — 2026-09-27
**Three small fixes to the command line.** A mistyped `records --width` now says so instead
of looking like an empty result, the simulator's row in `devices` loses a stray `()`, and
two help pages read like the rest.
### Fixed
- **`meshterm records --width` refuses a width that doesn't exist.** Hash widths are 1, 2,
or 4. Any other number used to look like an empty result, so a typo and an empty
database gave the same answer, and `--width 0` was ignored and listed every width. Now
it's a usage error that names the three choices, like `--category` already was.
- **The simulator's row in `meshterm --mock devices` no longer ends in an empty `()`.**
The brackets hold a serial port, and the simulator doesn't have one.
- **`meshterm platform --help` and `meshterm specimen --help` read like the others.** They
used to print notes meant for someone reading the source code, markup and all, and cut
their summaries short in `meshterm --help`. Now each has one plain line.
## [0.10.0] — 2026-09-25
**Regions, a uConsole with no bridge, and Bluetooth that works on Linux.** MeshTerm now
@@ -188,6 +206,7 @@ exact commands.
MeshTerm is free and open source under the Apache 2.0 licence. The name and the logo are
not covered by the licence (see `NOTICE`), so a fork is welcome under its own name.
[Unreleased]: https://github.com/jpmartineau/MeshTerm/compare/v0.10.0...HEAD
[Unreleased]: https://github.com/jpmartineau/MeshTerm/compare/v0.10.1...HEAD
[0.10.1]: https://github.com/jpmartineau/MeshTerm/releases/tag/v0.10.1
[0.10.0]: https://github.com/jpmartineau/MeshTerm/releases/tag/v0.10.0
[0.9.0]: https://github.com/jpmartineau/MeshTerm/releases/tag/v0.9.0
+60 -2
View File
@@ -25,8 +25,66 @@ this repository.
seen, etc.), UX standards for screens/dialogs/menus, and where the reusable building
blocks live in code (`ui/menus.py`, `ui/markdown.py`, `ui/widgets.py`, `ui/theme.py`).
New code should read like it always belonged here.
- Small fixes can go straight to a pull request. For anything bigger, open an issue
first and wait for a yes before writing a lot of code — saves both of us a rewrite.
- Tell us before you start working on something (see below). For anything bigger than a
small fix, expect to talk it through first. That saves everyone a rewrite.
## Who can contribute
If you use MeshTerm, you're welcome here. Maybe something broke for you, or you wished
it could do one more thing, or a screen looked wrong on your radio. Those are exactly
the changes we want.
### How MeshTerm was made
Some people in the MeshCore community don't trust code written by AI. After some of
what has happened lately, that's easy to understand, and it deserves a straight answer.
Jean-Pierre Martineau, who started MeshTerm, sees it differently. AI was a big help on
this project. Honestly, MeshTerm couldn't have been built without it. But the choices
were his. He decided what MeshTerm should be, what it should do, and how it should
look. He uses it every day. It's handmade, with AI's help.
The project wants to stay that way. That's what the rules below are for.
### Come say hello first
Using AI tools to help you work is fine. What matters is that a real person who cares
about MeshTerm is behind the change.
The MeshTerm community lives on [Discord](https://discord.gg/AZwe5Uvb3S), and everyone
is welcome to join. But please join before you change things. Before you start writing
code, say hello in the **#contributing** channel and tell us what you'd like to work on.
Then wait until we say yes. This goes for small fixes too.
When you open your pull request, include two things:
- **Your Discord username**, the one you use on the MeshTerm server.
- **A link to the message where we said yes.** In Discord, right-click the message and
choose "Copy Message Link."
If you haven't joined the Discord, or nobody said yes to your idea, your pull request
will be closed without a review.
### No bots
Some accounts use AI to find open issues, write a fix, and open a pull request, all on
their own. There's no person behind it who read the change or can answer questions
about it. People call this "AI slop." We close those pull requests without a review,
even if the code looks fine. The same goes for issues and bug reports written that way.
### Tell us if you used AI
This part is required. If an AI tool wrote any part of your change, say so in two
places:
- **In each commit it helped with.** Add a line at the end of the commit message naming
the tool, like `Co-Authored-By: Claude <noreply@anthropic.com>` or
`Assisted-by: GitHub Copilot`.
- **In your pull request.** Write a sentence about what the tool did and what you
checked yourself.
If you used AI and didn't say so, we'll treat your pull request as a bot's. Saying so
costs you nothing. Either way, you're the author, so be ready to explain your change.
## Making a change
+9 -7
View File
@@ -23,8 +23,8 @@ your mesh's history is worth.
<a href="https://discord.gg/AZwe5Uvb3S"><img alt="Discord" src="https://img.shields.io/badge/chat-Discord-5865F2"></a>
</p>
> MeshTerm is a side project, run by one person. Bug reports are very welcome. Small fixes
> can go straight to a pull request; for anything bigger, please open an issue first.
> MeshTerm is a side project, run by one person. Bug reports are very welcome. Want to
> change something? Say hello on [Discord](https://discord.gg/AZwe5Uvb3S) first.
> [More on how it's run](#how-this-project-is-run).
https://github.com/user-attachments/assets/f91a6695-14d5-4ae0-8fdc-e65492d0955d
@@ -297,11 +297,13 @@ fair bit, and that's on purpose: how well a problem is described really does dec
I can do anything with it. If I can reproduce it, I'll usually chase it. If I can't, I'm
mostly guessing.
**Ask before you write a big pull request.** Small fixes can go straight to a pull
request. For anything bigger, open an issue first and wait for a yes. I'm not being
precious — MeshTerm has firm house rules about how screens get built (they're in
[CLAUDE.md](CLAUDE.md)), and I'd hate for you to spend a weekend on something I then ask
you to rewrite. A quick conversation first saves us both.
**Ask before you write a pull request.** Join the [Discord](https://discord.gg/AZwe5Uvb3S)
and say hello in `#contributing`. Tell us what you'd like to change. Then wait for a yes.
That goes for small fixes too. I'm not being precious. MeshTerm has firm house rules
about how screens get built (they're in [CLAUDE.md](CLAUDE.md)), and I'd hate for you to
spend a weekend on something I then ask you to rewrite. A quick conversation first saves
us both. AI tools are fine, as long as you say you used them.
[CONTRIBUTING.md](CONTRIBUTING.md) has the details.
**I can't promise timelines.** Some things get fixed the same night. Some sit for a month
because life happened. If your issue goes quiet, please give it a nudge. That helps me.
+1 -1
View File
@@ -32,7 +32,7 @@ USB, Bluetooth, or Wi-Fi needs none of this.
| --- | --- |
| [How the code is laid out](architecture.md) | The layering, and where a new feature goes. |
| [CLAUDE.md](../CLAUDE.md) | The actual style guide — terminology, UX standards, and the reusable building blocks. Written for AI assistants, and the house rules for everyone. |
| [CONTRIBUTING.md](../CONTRIBUTING.md) | The dev install, the three gates a change has to pass, and what happens to your contribution's licensing. |
| [CONTRIBUTING.md](../CONTRIBUTING.md) | How to propose a change, the rules on AI use, the dev install, the three checks a change must pass, and what happens to your contribution's licensing. |
## Historical record
+1 -1
View File
@@ -1556,7 +1556,7 @@ each link; the scripted face prints the link alone.
**`--json`:**
```json
{"page":"discord","title":"Join Discord","version":"0.10.0","text":"Join the Discord\n Questions, ideas, bug reports, and mesh talk.\n\n • https://discord.gg/AZwe5Uvb3S","links":["https://discord.gg/AZwe5Uvb3S"]}
{"page":"discord","title":"Join Discord","version":"0.10.1","text":"Join the Discord\n Questions, ideas, bug reports, and mesh talk.\n\n • https://discord.gg/AZwe5Uvb3S","links":["https://discord.gg/AZwe5Uvb3S"]}
```
| Field | Type | Meaning |
+10 -5
View File
@@ -33,7 +33,7 @@ That's [further down](#always-on-the-bridge) — most people want the section ab
- [Step 2 — Install the radio library](#step-2--install-the-radio-library)
- [Step 3 — Install MeshTerm](#step-3--install-meshterm)
- [Step 4 — Connect](#step-4--connect)
- [Moving over from the bridge](#moving-over-from-the-bridge)
- [Moving over from the bridge (0.9.0 and earlier)](#moving-over-from-the-bridge-090-and-earlier)
- [Board wiring](#board-wiring)
- [Troubleshooting](#troubleshooting)
- [Always on: the bridge](#always-on-the-bridge)
@@ -215,11 +215,16 @@ The AIO's wiring is the default, so this profile needs nothing beyond `transport
---
## Moving over from the bridge
## Moving over from the bridge (0.9.0 and earlier)
Already running the bridge? The first time you connect with `--spi` (or a profile, or the
picker row), MeshTerm carries the bridge's node over — **copying**, never moving, so the
bridge still works afterwards if you go back to it:
> **Only if you set up the bridge with MeshTerm 0.9.0 or earlier.** Before 0.10.0 the bridge
> was the only way to use the uConsole's radio, so an older setup is almost certainly
> running one. On a fresh install of 0.10.0 or newer there is nothing to move over — skip
> ahead to [Board wiring](#board-wiring).
Upgraded from 0.9.0 or earlier with the bridge still installed? The first time you connect
with `--spi` (or a profile, or the picker row), MeshTerm carries the bridge's node over —
**copying**, never moving, so the bridge still works afterwards if you go back to it:
- the identity key — from the `meshcore-console` GUI's copy first
(`~/.local/share/meshcore-uconsole/identity.key`), the way the bridge itself looks for
+1 -1
View File
@@ -6,7 +6,7 @@ Connects to a companion over USB, Bluetooth, or TCP. For Windows, macOS, and Lin
from datetime import datetime
__version__ = "0.10.0"
__version__ = "0.10.1"
__author__ = "Jean-Pierre Martineau"
#: The year MeshTerm was first published; the copyright span starts here.
+8 -2
View File
@@ -603,7 +603,10 @@ def _report_no_windows_console(console: Console) -> None:
console.print("try [accent]meshterm --help[/accent].")
@app.command(name="platform")
@app.command(
name="platform",
help="Show which platform MeshTerm runs as, and why",
)
def platform_command() -> None:
"""Print the resolved platform and every input the resolution looked at.
@@ -650,7 +653,10 @@ def platform_command() -> None:
)
@app.command(name="specimen")
@app.command(
name="specimen",
help="Print every mark, icon, and colour MeshTerm draws",
)
def specimen_command() -> None:
"""Print the visual-language specimen for the active platform.
+2
View File
@@ -273,6 +273,8 @@ class DiscoveredDevice:
if self.is_spi:
return f"{self.name or 'SPI radio'} ({self.port})"
name = self.product or self.description or self.vendor_label or "Serial device"
if not self.port: # the simulator has nothing to point at, so no "()"
return name
suffix = f"({self.port})"
if name.endswith(suffix): # avoid "… (COM11) (COM11)"
name = name[: -len(suffix)].rstrip()
+10 -1
View File
@@ -26,6 +26,9 @@ from ..services.records import CATEGORIES, CATEGORY_BY_ID, Category
from ..ui.script import NONE
from .base import Tool, ToolResult, register
#: The hash widths a record can be set at: MeshCore's path hashes are 1, 2 or 4 bytes.
WIDTHS = (1, 2, 4)
if TYPE_CHECKING: # pragma: no cover - typing only
from ..persistence.repository import DiscoveredPath
from ..ui.fields import NodeRef
@@ -183,7 +186,13 @@ class TrophyCaseTool(Tool):
f"--category must be one of: {choices} (got {category!r})"
)
tool_params["category"] = category
if width:
if width is not None:
# The same closed set as --category, for the same reason: `--width 3`
# found nothing and exited 5, indistinguishable from an empty database,
# and `--width 0` was falsy and so quietly listed every width.
if width not in WIDTHS:
choices = ", ".join(map(str, WIDTHS))
raise typer.BadParameter(f"--width must be one of: {choices} (got {width})")
tool_params["width"] = width
run_tool_command(self, tool_params)
+55 -1
View File
@@ -566,8 +566,19 @@ def test_a_destructive_command_without_yes_is_a_silent_usage_error(run, args) ->
[
(("contacts", "--sort", "bogus"), "--sort"),
(("records", "--category", "long_hual"), "--category"),
(("records", "--width", "3"), "--width"),
(("records", "--width", "0"), "--width"),
(("records", "--width", "-1"), "--width"),
(("records", "--json", "--width", "3"), "--width"),
],
ids=[
"contacts-sort",
"records-category",
"records-width-3",
"records-width-0",
"records-width-negative",
"records-width-3-json",
],
ids=["contacts-sort", "records-category"],
)
def test_a_value_outside_a_closed_set_is_refused_rather_than_ignored(run, args, flag) -> None: # noqa: ANN001
"""A typo in a closed-set option used to be answered with a different question.
@@ -754,6 +765,49 @@ def test_a_closing_message_goes_to_stderr_too(run) -> None: # noqa: ANN001
assert "records stored" in result.stderr
@pytest.mark.parametrize("width", ["1", "2", "4"])
def test_every_real_hash_width_is_accepted(run, width) -> None: # noqa: ANN001
"""Refusing the widths that don't exist must not refuse the three that do."""
result = run("records", "--width", width)
assert result.exit_code == exitcodes.NO_RESULT, result.stderr
assert "records stored" in result.stderr
def test_the_simulator_is_listed_without_empty_brackets(run) -> None: # noqa: ANN001
"""``--mock devices`` named the simulator ``… (nothing transmits) ()``: a port it lacks."""
result = run("devices")
assert result.exit_code == exitcodes.OK, result.stderr
assert "the built-in simulator (nothing transmits)" in result.stdout
assert "()" not in result.stdout
#: What a docstring written for the source leaks into ``--help`` when Typer is left to
#: use it: a Sphinx role, reST's double backticks, or a path into the package.
_SOURCE_NOTES = re.compile(r":(func|mod|class|meth|attr|data):`|``|meshterm/[\w/]+\.py")
@pytest.mark.parametrize("leaf", _leaf_commands(), ids=lambda leaf: "-".join(leaf))
def test_every_command_help_is_written_for_the_terminal(run, leaf) -> None: # noqa: ANN001
"""A help page is for someone at a prompt asking what a command does.
``platform`` and ``specimen`` printed their whole docstrings — notes for whoever reads
``cli.py``, Sphinx roles and source paths included — and the same markup cut their
one-line summaries short in ``meshterm --help``.
"""
result = run(*leaf, "--help")
assert result.exit_code == exitcodes.OK, result.output
assert not _SOURCE_NOTES.search(result.output), result.output
def test_the_command_list_has_no_source_notes(run) -> None: # noqa: ANN001
"""The one-line summaries in ``meshterm --help`` come from the same help text."""
result = run("--help")
assert not _SOURCE_NOTES.search(result.output), result.output
for line in result.output.splitlines():
if line.split()[:1] in (["platform"], ["specimen"]):
assert not line.rstrip().endswith("..."), f"summary cut short: {line!r}"
# -- the simulator's own history ------------------------------------------------------
+16 -1
View File
@@ -14,7 +14,7 @@ import pytest
from meshterm.core.config import DeviceProfile
from meshterm.core.device_store import DeviceStore
from meshterm.core.discovery import DiscoveredDevice, discover_devices
from meshterm.core.discovery import TRANSPORT_MOCK, DiscoveredDevice, discover_devices
from meshterm.core.selection import DeviceSelectionError, resolve_device
@@ -112,6 +112,21 @@ def test_label_does_not_repeat_the_port() -> None:
assert DiscoveredDevice("COM5", product="Wio SX1262").label == "Wio SX1262 (COM5)"
def test_a_device_with_no_port_gets_no_empty_brackets() -> None:
"""The simulator has nothing to point at, so its label ends at its name, not in "()".
The serial branch is also where every transport without one of its own lands, and it
appended ``({port})`` whether or not there was a port.
"""
sim = DiscoveredDevice(
transport=TRANSPORT_MOCK,
description="the built-in simulator (nothing transmits)",
name="simulator",
)
assert sim.label == "the built-in simulator (nothing transmits)"
assert DiscoveredDevice(product="Wio SX1262").label == "Wio SX1262"
def test_stable_id_precedence() -> None:
"""stable_id prefers serial number, then vid:pid, then the port name."""
assert DiscoveredDevice("COM5", serial_number="SN1", vid=1, pid=2).stable_id == "sn:SN1"