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>
The 46 missing docstrings, written rather than generated.
Most of the test ones already carried the explanation — as a comment sitting on
the first line of the body. That prose is the docstring; it was only in the
wrong place, so the config editor's PIN and lane tests keep their own words and
lose the duplicate comment. The rest are new: the two `repo` fixtures, the
counting `FakeDevice` in `test_device_state` (each method now says what it
answers *and* that it counts the call), the PicoCalc chrome and 16-slot theme
tests, and three one-offs.
In the package: `PathLine` and `FastRenderer`'s constructors point at the class
docstring that already documents their arguments rather than repeating it,
`SelectItem.__post_init__` and `courier_screen`'s opener say what they cache and
why, and `emoji_width.create_content` explains the memo it wraps the line lookup
in. `flash.py` and `uart1-mux.py` get theirs too, and `tests/__init__.py`
finally says what the package is.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The target picker was a hand-rolled two-section list ("Recently traced",
then "Contacts") — the one contact enumerator in the app that didn't ride
the shared ContactListScreen. Move it onto that widget so it sorts,
filters, and steers exactly like the Contacts screen and the courier
recipient: full NAME · HEARD · PKTS · KEY lanes, Ctrl+arrow sort ring,
type-to-filter. Every node type is listed — a trace answers "can I reach
this node?" for a repeater or room, not just a companion.
The recency the old section carried becomes a first-class sortable lane:
ContactListScreen grows an optional TRACED column (opt-in via show_traced)
between name and heard, showing how long ago each node was last traced,
recency-heat coloured. It is the picker's default sort, descending, so the
most-recently-traced node leads and never-traced ones gather at the
bottom. A new repository query (target_last_traced) feeds it, folded onto
contacts the same way the old recent-targets list was.
The free-text prompt stays as the fallback when there are no contacts to
list — still the only way to trace a bare key prefix off-list.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route graph (both the record card and the Message paths dialog):
- Relays now wear their own map marker — ▲ repeater, ■ room, ◉ sensor —
instead of a generic ●, via a node-type resolver threaded into
route_graph_style (built from contacts; an untyped relay keeps the dot).
- A shared node-type key (★ you ▲ repeater ● node ■ room ◉ sensor) sits
under every fan, minted once as widgets.node_type_legend.
- The drawn path is coloured by SNR (the SNR palette): the record's walk
by its weakest link, the Message paths selection by its reception SNR;
unselected paths stay gray. Per-path, not per-hop — overheard frames
only carry the reception reading.
Record card:
- New 'reliability' stat: the success rate of traces aimed at the walk's
far node (the one delivery figure history can answer — a failed trace
records its target, not its route), with the sample count beside it.
- Area drawing reworked per feedback: our star is drawn last so it is
never hidden, hops are pinned in their node-name hue (star stays
yellow), the fill is one flat tone (even-odd shades only the enclosed
side; no dimmer interior wash), and the box is larger.
New shared helpers: theme.snr_rgb, widgets.name_rgb,
trace_runner.make_node_type_resolver, repository.target_trace_counts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A trace is one walked path and "target" is pure UX (hardware-verified),
so the feature now splits along that line: Trace target keeps the
symmetric mirror-and-return flow (picker, Explore paths), while the new
Trace path skips the picker entirely and walks a hand-composed route —
no Auto, no Explore, Trace gated until a path exists, history recorded
under the "(path)" sentinel so composed walks never pollute the target
picker. The composer loses its mode toggle: a pinned target means
mirrored, no target means verbatim, and its preview opens as us → us.
Sampling returns, deliberately: a Sample count action (1/2/3/5/8) runs
that many traces per commit, paced by trace_cooldown_s between sends and
counted off on the in-flight dialog — never a burst. Actions reorder to
compose/explore/width/samples/trace with a Back row; the w/p/x hotkeys
retire.
Display: route nodes drop the underscore (prompt_toolkit draws U+00A0 as
"_", so name (hash) now joins with plain spaces), the composer renders
nodes exactly like the route line, the (previous · …) stamp gets its own
line, the path row ends with the displayed hop count, Explore rows lead
with the route instead of an "observed path" label, and the empty Traces
placeholder is gone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four trace-feature upgrades, all UX-preserving:
- Stored traces recover their path-hash width from the (uniform) hop
hashes at rehydration, so a target's previous route renders short
hashes instead of the device's full 64-char public key.
- The trace screen's verbs are selectable action rows now — Trace,
Path width, Compose path, Explore paths. The cursor opens on Trace so
plain Enter still just traces; p/x/w hotkeys stay on their rows, and
the cursor only pins the view while it's actually being moved, so
PgUp/PgDn still read a long trace log freely.
- A floating path-width dialog picks the 1/2/4/8-byte hop addressing
(each row previews the target key with the addressed slice lit, the
device's routing width marked). Changing it re-renders a standing
forced path — hops widened back through their canonical hashes — and
shapes every spec composed or explored afterwards.
- The composer's new ⇄ row toggles symmetric (return mirrors the
outbound, the current behaviour) against asymmetric, where the whole
walk is composed by hand — target routed *through*, outbound
repeaters reusable, only the landing back on us implicit. Toggling
materializes the mirror into editable hops (or regenerates it from
what precedes the target), so the wire route never changes under the
toggle; the planned-route preview dims only palindromic return legs.
Also: the target now carries its hash in the composer preview like
every other node, and the composer's footer hint reads briefer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Repeaters penalize (and can blacklist) nodes that burst traffic, so
every trace-transmitting surface now sends exactly one transmission
per human action: Enter on the live screen runs a single trace (the
screen still aggregates the session's medians), the scenario probe
measures each candidate path once, and the CLI drops --samples to
become a true one-shot. trace_runner.measure and the s-key sample
cycle go with it.
Also de-pollute the target picker's "Recently traced" section: the
repository now returns a short recency-ordered list instead of the
ever-growing all-time tally, and stored hex prefixes fold back to
their contact names so a node shows once.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five features leave the menu and CLI: the heard-nodes review, the
baseline-regression checker, the offline link-budget calculator, the
route-stability graph, and the run-history browser — along with their
services (link_quality, link_budget, route_stability), the pyvis route
renderer, and the pyvis/networkx dependencies.
The data layer is untouched where it still earns its keep: passive
monitoring keeps recording every overheard packet (the header counters,
Nodes, and Map read it, and `meshterm monitor` still captures a bounded
window from the CLI), run logging keeps writing `runs` rows, and trace
history stays queryable for the trace tools. Only the route-map-only
`all_traces` query goes.
Also fixes a latent tie-break flake in the nodes sort: ages are now
measured from one clock snapshot, so identical last-seen stamps really
tie and keep their A→Z order in both directions.
Co-Authored-By: Claude Fable 5 <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>
19 tests covering the persistence layer: field round-trip for successful
and failed traces (hops, RTT, tx_power, timestamp), recent_traces
ordering/limit/target filtering, all_traces cross-target span,
traced_targets count-ordering, latest_trace success_only variants,
and full run lifecycle (start → finish, error without summary, limit,
ordering).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>