Each test docstring states the behaviour that the test proves, in the
simple present. No assertion message, test id, or other string literal
changed. Four statements now agree with the code: the Trace highlight
stays on the last row, a menu count, and two others.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
JP moved to 3.14. Every dependency installs on it -- the Windows Bluetooth packages
included -- and the suite passes: 2705 passed, 1 skipped, on 3.14.7 as on 3.10.
- pyproject gains the 3.14 classifier. requires-python stays >=3.10: the uConsole on
bookworm runs 3.11, and a pip or pipx install on an older system should keep working.
- CI keeps its shape, which is Linux over the whole range and the other two runners at
its ends. Linux gains a 3.14 leg, and 3.14 replaces 3.13 as the top end on Windows and
macOS.
- Three tests reached a loop through asyncio.get_event_loop_policy(), which 3.14
deprecates and 3.16 removes, and never closed the loop they made. The two countdown
tests now make one with new_event_loop() and close it; the sweep test uses
asyncio.run(), which does both. On 3.14 the suite is back to no warnings.
The standalone installers still bundle 3.10; moving their build to 3.14 is for 0.9.1.
Co-Authored-By: Claude Opus 5 (1M context) <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>
`ruff format .` across the repo: 153 files, no behaviour change. This is the
second gate CONTRIBUTING.md promises, and until now it reported 154 files needing
reformat — so a first contributor ran the command the guide told them to run and
could not tell their own diff from the background.
Landed on its own, with nothing else in it, because a whole-repo reformat is only
safe to skip past in blame if it is provably nothing but layout. It was checked
that way rather than assumed: every changed file was formatted in memory and
compared by full `ast.dump()`, and 152 of 153 come out structurally identical
(the one exception was a docstring opening on a quote character, fixed in the
commit before this one). The string and comment token streams were diffed
separately, so a quote flip or an edited comment could not hide inside a
re-layout — five quote-style normalisations and sixteen implicit-concatenation
joins, all value-identical, and one commented-out line gaining the space after
its `#`. `meshterm specimen` is byte-identical on both platforms, and the suite
is 1888 green, gallery gate and `test_theme16` included.
What actually changed, in order of how much of the diff it is: wrapped calls and
signatures rejoin onto one line where they fit inside the hundred columns; the
magic trailing comma expands what it is there to expand; complex slices gain
their spaces (`[len(noise) :]`); a few redundant parens go. The tables that are
laid out to be read were fenced in the previous commit and are untouched here.
The aligned trailing-comment column is gone from about ninety lanes that were not
fenced — including the `heat.*` ladder in `MESH_THEME`, which cannot be fenced
where it sits. Every comment stays on the entry it describes, so nothing there
reads wrong; it just no longer lines up.
Add this commit to `.git-blame-ignore-revs`, which follows immediately.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`ruff check --fix` over the whole tree, ahead of putting the linter in CI:
1977 mechanical fixes, overwhelmingly `Optional[X]` → `X | None` (1186 in the
package alone) and the quoted annotations that `from __future__ import
annotations` already made unnecessary, plus import sorting and unused imports.
Nothing here changes behaviour — the suite is 1885 green before and after.
Four findings were real rather than stylistic and are fixed by hand: a dead
`cells` range in `MapCanvas`, a dead slot dict in the heat-ladder test, a
`ProgressScreen` task handle nobody read, and a tab loop binding a `label` it
never used. The two `F821`s were quoted forward references to names imported
inside their function bodies — now declared under `TYPE_CHECKING`, which keeps
`test_performance`'s local import (it guards the startup import graph) while
letting the linter resolve the name.
412 findings remain and are deliberately untouched: they need a decision about
the rule, not an edit to the code.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The wait dialog drew its three lines flush left while every other dialog in
the app centers its body — so the Cancel chip sat against the border, reading
as an afterthought beside the button rows it is a sibling of. It now centers
line by line the way ButtonDialog and ProgressDialog do, and takes the same
+12 breathing room in its content-sized width (49 cells at its widest, still
inside the PicoCalc's 53).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every feature paced its own bursts privately, so nothing knew what anything else
had just put on the air, and a wait was a frozen screen -- which on a radio app
reads as a dead link rather than as courtesy.
There is a shared clock now. Every real transmission marks it, in the five send
methods that are the only places the radio is actually spoken to (the simulator
marks the same points, so the whole thing is walkable with no hardware). Anything
about to transmit can ask what is still owed.
Two clocks, because two kinds of transmission cost the mesh very differently. The
general one is the transmit cooldown, whose minimum is now 0.1s -- a floor, not an
off switch; there is no zero for duty-cycle courtesy. Flood adverts get their own,
floored at 5s and defaulting to a minute: every repeater in range rebroadcasts one,
and it waits out both clocks.
Under two seconds the wait just happens -- a dialog that flashes up is itself the
interruption. Over it, a countdown floats over whatever asked: the seconds left,
why it is waiting, and a Cancel chip. Enter and Esc both take it, sharing one hint
atom because they do the one thing on offer, and cancelling abandons the action
rather than merely the wait. It resolves itself at zero.
The suite resets the clock between tests. It is process-wide, which is right for a
session and wrong for a run: one test sending something left a cooldown standing
for the next, whose scripted flow then stopped on a countdown nobody wrote.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>