Every line fits the hundred columns it was configured for
`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>
This commit is contained in:
1 parent
ca0023f5b7
commit
b5006cc415
39 files changed
+188
-81
No files matched your search
@@ -18,5 +18,9 @@ def copyright_notice() -> str:
|
||||
never drift apart.
|
||||
"""
|
||||
year = datetime.now().year
|
||||
span = str(COPYRIGHT_START_YEAR) if year <= COPYRIGHT_START_YEAR else f"{COPYRIGHT_START_YEAR}-{year}"
|
||||
span = (
|
||||
str(COPYRIGHT_START_YEAR)
|
||||
if year <= COPYRIGHT_START_YEAR
|
||||
else f"{COPYRIGHT_START_YEAR}-{year}"
|
||||
)
|
||||
return f"Copyright © {span} {__author__}"
|
||||
@@ -300,7 +300,11 @@ def _telemetry_apply(field_name: str) -> Callable[[Device, Any, dict], Awaitable
|
||||
"""Build an apply that updates one telemetry mode, preserving the others."""
|
||||
|
||||
async def apply(device: Device, value: Any, snapshot: dict) -> None:
|
||||
base = value if field_name == "telemetry_mode_base" else snapshot.get("telemetry_mode_base", 0)
|
||||
base = (
|
||||
value
|
||||
if field_name == "telemetry_mode_base"
|
||||
else snapshot.get("telemetry_mode_base", 0)
|
||||
)
|
||||
loc = value if field_name == "telemetry_mode_loc" else snapshot.get("telemetry_mode_loc", 0)
|
||||
env = value if field_name == "telemetry_mode_env" else snapshot.get("telemetry_mode_env", 0)
|
||||
await device.set_telemetry_modes(int(base or 0), int(loc or 0), int(env or 0))
|
||||
|
||||
@@ -625,7 +625,9 @@ class TraceResult:
|
||||
origin = device_label
|
||||
for hop in self.hops:
|
||||
destination = hop.node or device_label
|
||||
edges.append(HopEdge(index=hop.index, origin=origin, destination=destination, snr=hop.snr))
|
||||
edges.append(
|
||||
HopEdge(index=hop.index, origin=origin, destination=destination, snr=hop.snr)
|
||||
)
|
||||
origin = destination
|
||||
return edges
|
||||
|
||||
|
||||
@@ -213,7 +213,8 @@ class Link:
|
||||
if snr is not None:
|
||||
span = _SNR_CEIL_DB - _SNR_FLOOR_DB
|
||||
frac = (snr - _SNR_FLOOR_DB) / span
|
||||
quality = min(_QUALITY_MAX, max(_QUALITY_MIN, _QUALITY_MIN + frac * (_QUALITY_MAX - _QUALITY_MIN)))
|
||||
reach = _QUALITY_MIN + frac * (_QUALITY_MAX - _QUALITY_MIN)
|
||||
quality = min(_QUALITY_MAX, max(_QUALITY_MIN, reach))
|
||||
return evidence * recency * quality
|
||||
|
||||
|
||||
@@ -718,7 +719,9 @@ class MeshTopology:
|
||||
out.sort(key=lambda s: (-s.strength, s.node))
|
||||
return out
|
||||
|
||||
def scenarios(self, target: str, *, device_route: tuple[str, ...] | None = None) -> list[PathScenario]:
|
||||
def scenarios(
|
||||
self, target: str, *, device_route: tuple[str, ...] | None = None
|
||||
) -> list[PathScenario]:
|
||||
"""Rank the candidate outbound routes for reaching ``target``.
|
||||
|
||||
Three families, deduplicated in this priority order:
|
||||
|
||||
@@ -327,8 +327,9 @@ def trace_timeout(hop_count: int) -> float:
|
||||
:data:`TRACE_TIMEOUT_CEILING_S`.
|
||||
|
||||
``hop_count`` is the number of hops in the *transmitted* path, which already includes
|
||||
the mirrored return leg (see :meth:`~meshterm.core.connection.MeshCoreDevice._trace_path_to_contact`),
|
||||
so it maps one-to-one onto relay transmissions and must not be doubled here.
|
||||
the mirrored return leg (see
|
||||
:meth:`~meshterm.core.connection.MeshCoreDevice._trace_path_to_contact`), so it maps
|
||||
one-to-one onto relay transmissions and must not be doubled here.
|
||||
|
||||
Args:
|
||||
hop_count: Hops in the walked path (return leg included). ``0`` (or less) means the
|
||||
|
||||
@@ -59,7 +59,8 @@ class ChannelsTool(Tool):
|
||||
|
||||
changes = await manage_channels(ctx)
|
||||
message = (
|
||||
f"[ok]✓[/ok] applied [brand]{changes}[/brand] channel change{'' if changes == 1 else 's'}"
|
||||
f"[ok]✓[/ok] applied [brand]{changes}[/brand] "
|
||||
f"channel change{'' if changes == 1 else 's'}"
|
||||
if changes
|
||||
else None
|
||||
)
|
||||
|
||||
@@ -233,7 +233,9 @@ class CourierTool(Tool):
|
||||
help=self.help, no_args_is_help=True, rich_markup_mode="rich"
|
||||
)
|
||||
|
||||
@courier_app.command("queue", help="Queue a message for delivery when the contact is next heard")
|
||||
@courier_app.command(
|
||||
"queue", help="Queue a message for delivery when the contact is next heard"
|
||||
)
|
||||
def _queue_cmd(
|
||||
contact: str = typer.Argument(..., help="The recipient contact's name"),
|
||||
text: list[str] = typer.Argument(..., help="The message to queue"),
|
||||
|
||||
@@ -285,7 +285,9 @@ class TraceTool(Tool):
|
||||
"""
|
||||
from ..cli import run_tool_command
|
||||
|
||||
@app.command(name=self.name, help="Run a single path trace to a target and show per-hop SNR")
|
||||
@app.command(
|
||||
name=self.name, help="Run a single path trace to a target and show per-hop SNR"
|
||||
)
|
||||
def _trace(
|
||||
target: str = typer.Option(..., "--target", "-t", help="Target node name/prefix"),
|
||||
path: str | None = typer.Option(
|
||||
|
||||
@@ -272,7 +272,9 @@ class TxOptimizeTool(Tool):
|
||||
if int(params.get("samples", 3)) > MAX_SAMPLES:
|
||||
ctx.ui.note(f"[warn]capping at {MAX_SAMPLES} traces per level[/warn]")
|
||||
|
||||
ctx.ui.note(f"[muted]logging in to[/muted] [brand]{admin_node.name}[/brand] [muted]…[/muted]")
|
||||
ctx.ui.note(
|
||||
f"[muted]logging in to[/muted] [brand]{admin_node.name}[/brand] [muted]…[/muted]"
|
||||
)
|
||||
await self._login(ctx, admin_node, params)
|
||||
|
||||
ctx.ui.note(
|
||||
|
||||
+2
-1
@@ -181,7 +181,8 @@ def _state_intensity(row: str) -> str:
|
||||
bright = False
|
||||
elif part == "1":
|
||||
bright = True
|
||||
if any(_DIM_FG.fullmatch(p) for p in parts) and not any(p in _SAYS_INTENSITY for p in parts):
|
||||
dim = any(_DIM_FG.fullmatch(p) for p in parts)
|
||||
if dim and not any(p in _SAYS_INTENSITY for p in parts):
|
||||
out.append("\x1b[" + ";".join(["1" if bright else "22"] + parts) + "m")
|
||||
else:
|
||||
out.append(found.group())
|
||||
|
||||
@@ -557,7 +557,9 @@ def _draw_tile(frame: _Frame, layers: list[Layer], z: int, x: int, y: int) -> No
|
||||
if len(ring) >= 2:
|
||||
vp.draw_line(project(ring, waterway.extent), rgb, prio)
|
||||
if feat.name and not frame.coarse:
|
||||
_add_line_label(frame, feat.rings, waterway.extent, z, x, y, feat.name, _WATER_LABEL)
|
||||
_add_line_label(
|
||||
frame, feat.rings, waterway.extent, z, x, y, feat.name, _WATER_LABEL
|
||||
)
|
||||
|
||||
# Roads / rail.
|
||||
transportation = by_name.get("transportation")
|
||||
|
||||
+3
-2
@@ -1030,8 +1030,9 @@ async def _auto_reconnect(ctx: AppContext, dialog: ReconnectDialog) -> None:
|
||||
|
||||
The dead link is released up front (:meth:`~meshterm.context.AppContext.release_link`) so
|
||||
the peripheral is free to advertise while we wait for it. Each transport then waits for its
|
||||
own evidence that the device is reachable before touching the radio, so an attempt is only ever made against something that is actually there. Serial
|
||||
waits for the OS to re-enumerate the port the connection was opened on. Bluetooth listens
|
||||
own evidence that the device is reachable before touching the radio, so an attempt is only
|
||||
ever made against something that is actually there. Serial waits for the OS to re-enumerate
|
||||
the port the connection was opened on. Bluetooth listens
|
||||
for the companion's advertisement (:func:`~meshterm.core.discovery.find_ble_device`), which
|
||||
is the same question asked of the only registry BLE has, and hands the resulting live
|
||||
handle to :meth:`~meshterm.context.AppContext.reconnect` — a power-cycled peripheral is a
|
||||
|
||||
@@ -365,8 +365,9 @@ class NodeDetailScreen(Screen):
|
||||
:meth:`selected_spec`); Esc resolves :data:`CANCEL` to leave.
|
||||
|
||||
Two axes of navigation, matching the app's spatial feel: ``Tab``/``Shift+Tab`` switches
|
||||
which view fills the stage, and ``↑↓`` moves the cursor *within* the active tab — through its route list (on the Routes tab, the selection drives the
|
||||
graph highlight and Enter arms a trace on the picked route) and action rows. The page
|
||||
which view fills the stage, and ``↑↓`` moves the cursor *within* the active tab — through
|
||||
its route list (on the Routes tab, the selection drives the graph highlight and Enter arms
|
||||
a trace on the picked route) and action rows. The page
|
||||
itself never scrolls: the identity header, tab strip, and stage are pinned, the stage
|
||||
is sized to the viewport, and the route list windows itself into the leftover rows —
|
||||
``PgUp/PgDn`` page the cursor through it, ``Home/End`` jump it to the ends.
|
||||
|
||||
@@ -874,7 +874,10 @@ class PacketViewer(Screen):
|
||||
readings = (entry.raw or {}).get("trace_snrs") if isinstance(entry.raw, dict) else None
|
||||
if readings:
|
||||
hops = len(readings)
|
||||
return f"{hops} hop{'' if hops == 1 else 's'} walked — a trace logs each leg, not its relays"
|
||||
return (
|
||||
f"{hops} hop{'' if hops == 1 else 's'} walked "
|
||||
"— a trace logs each leg, not its relays"
|
||||
)
|
||||
return "direct — no relays"
|
||||
|
||||
def _packet_rows(self, entry: PacketEntry) -> list[tuple[str, RenderableType]]:
|
||||
|
||||
@@ -645,7 +645,10 @@ class PathComposerScreen(Screen):
|
||||
entries.append((i, render_to_ansi(text, width)))
|
||||
if not any(kind == "hop" for kind, _ in rows):
|
||||
if any(kind == "fetch" for kind, _ in rows):
|
||||
note = "(no observed links from here — fetch the repeater's neighbours, or type a hex hash)"
|
||||
note = (
|
||||
"(no observed links from here — fetch the repeater's neighbours, "
|
||||
"or type a hex hash)"
|
||||
)
|
||||
else:
|
||||
note = "(no observed links from here — type a hex hash to force a hop)"
|
||||
# One entry per wrapped line: a multi-line string in a single entry would
|
||||
|
||||
@@ -155,9 +155,9 @@ _GRAPH_PAD_DOTS = 6
|
||||
#: the more so the shallower the curve's end tangent. Spanning the whole gap removes the corner,
|
||||
#: so the stroke stays an even, thin arc end to end; the nodes still seat level because the curve's
|
||||
#: end tangents are kept horizontal (see :data:`_BEND_K`), giving each marker a flat point to sit on
|
||||
#: without a platform run. (In a very tight column a full-lane shift is unavoidably steep right up to
|
||||
#: the node, so its marker sits on a gentle slope rather than dead level — the honest cost of keeping
|
||||
#: the stroke thin there.)
|
||||
#: without a platform run. (In a very tight column a full-lane shift is unavoidably steep right
|
||||
#: up to the node, so its marker sits on a gentle slope rather than dead level — the honest cost
|
||||
#: of keeping the stroke thin there.)
|
||||
_CURVE_SPAN = 1.0
|
||||
|
||||
#: The shortest horizontal run a lane change is given even for a one-lane hop, so a tight
|
||||
@@ -712,9 +712,9 @@ def _route(
|
||||
the eased level→curve→level of the shift. The shift spans the whole column gap
|
||||
(:data:`_CURVE_SPAN`) rather than a centred stretch flanked by flat platforms: a platform would
|
||||
meet the climbing curve at a corner, and that corner draws a heavy braille *knee*, so the curve
|
||||
runs continuously node to node instead and the marker seats on the curve's own level end tangent.
|
||||
The endpoints, sitting at the centre of the lane band, make the origin's diverging peels and
|
||||
us's converging merges fall out of this one rule — no endpoint special case.
|
||||
runs continuously node to node instead and the marker seats on the curve's own level end
|
||||
tangent. The endpoints, sitting at the centre of the lane band, make the origin's diverging
|
||||
peels and us's converging merges fall out of this one rule — no endpoint special case.
|
||||
|
||||
``vias`` are an edge's bypass waypoints (:func:`_bypass_vias`), threaded between the two
|
||||
markers in x order: the run applies the same level-or-shift grammar anchor to anchor —
|
||||
|
||||
@@ -24,8 +24,9 @@ drawn*, so every surface the survey found can eventually route through it:
|
||||
(:func:`cut_mark`), and that crack is what says the segment continues; only chips
|
||||
crack, an arrow line still ellipsizes. A row that also carries the app-wide
|
||||
opens-further-prompts ``…`` hangs it *outside* the budget
|
||||
(:func:`with_action_mark`) — chrome may not cost the route cells. ``auto`` (the default) picks powerline exactly
|
||||
when the terminal can draw it (:func:`~meshterm.ui.termfont.powerline_enabled` —
|
||||
(:func:`with_action_mark`) — chrome may not cost the route cells. ``auto`` (the default)
|
||||
picks powerline exactly when the terminal can draw it
|
||||
(:func:`~meshterm.ui.termfont.powerline_enabled` —
|
||||
a recommended font, a glyph-capable renderer, or the user's override) and falls
|
||||
back to arrows everywhere else, so no terminal ever sees tofu.
|
||||
* **Three overflow answers**, matching the three patterns the surfaces already use:
|
||||
@@ -606,7 +607,8 @@ class PathLine:
|
||||
nothing where the font has none).
|
||||
"""
|
||||
if plain:
|
||||
return [self._plain_hop(hop).cell_len for hop in hops], cell_len(self._separator), 0, 0, 0
|
||||
plain_cells = [self._plain_hop(hop).cell_len for hop in hops]
|
||||
return plain_cells, cell_len(self._separator), 0, 0, 0
|
||||
widths = [
|
||||
cell_len(hop.label) if hop.gap
|
||||
else self._chip(hop, self._chip_fill(hop)).cell_len
|
||||
@@ -916,9 +918,9 @@ class PathLine:
|
||||
The insertion slot outranks everything — it is the one chip that isn't a node,
|
||||
and it wears the ``cursor`` white so it reads as chrome among identities rather
|
||||
than as a hop with an unlucky hue (white sits outside the node spectrum, and the
|
||||
chip ink is already picked to stay readable on it). The fade comes next, outranking identity including
|
||||
our own: a dimmed hop is one nobody composed (an automatic landing back home, a
|
||||
mirrored return leg), and our end must recede with the rest of that automatic
|
||||
chip ink is already picked to stay readable on it). The fade comes next, outranking
|
||||
identity including our own: a dimmed hop is one nobody composed (an automatic landing
|
||||
back home, a mirrored return leg), and our end must recede with the rest of that automatic
|
||||
half rather than keep its yellow among the greys. Plain mode says the same thing
|
||||
by fading the name.
|
||||
"""
|
||||
|
||||
@@ -201,7 +201,7 @@ class Ui:
|
||||
banner: Any = None,
|
||||
footnote: str | None = None,
|
||||
) -> str | None:
|
||||
"""Ask for a Bluetooth companion's pairing PIN on the startup splash; ``None`` if cancelled."""
|
||||
"""Ask for a Bluetooth companion's pairing PIN on the splash; ``None`` if cancelled."""
|
||||
raise NotImplementedError
|
||||
|
||||
async def prompt_text_startup(
|
||||
|
||||
@@ -388,7 +388,9 @@ def _node_sections(
|
||||
)
|
||||
|
||||
out.append(Text())
|
||||
out.append(_heading("Rhythm", f"receptions by local time of day · {_slice_note(slice_minutes)}"))
|
||||
out.append(
|
||||
_heading("Rhythm", f"receptions by local time of day · {_slice_note(slice_minutes)}")
|
||||
)
|
||||
out.extend(
|
||||
axis_chart(
|
||||
timeline_rows(slots, rows=_CHART_ROWS), max(slots), len(slots) // 2,
|
||||
@@ -1152,7 +1154,7 @@ async def _routing_prefix_bytes(ctx: AppContext) -> int:
|
||||
async def _contact_resolvers(
|
||||
ctx: AppContext,
|
||||
) -> tuple[NodeResolver, Callable[[str | None], int | None], NodeResolver]:
|
||||
"""A ``(name, type, key)`` resolver trio over the device's contacts, best-effort like the prefix read.
|
||||
"""A ``(name, type, key)`` resolver trio over the contacts, best-effort like the prefix read.
|
||||
|
||||
All three fill the picker's (and arrivals') blanks from what the companion knows but the
|
||||
stored observations never carried: the name resolver names a node the device holds as a
|
||||
|
||||
@@ -1177,7 +1177,8 @@ class TraceScreen(Screen):
|
||||
rows.append(spin)
|
||||
elif self._status:
|
||||
rows.append(Text(self._status, style="err"))
|
||||
for number, trace in zip(range(len(self._traces), 0, -1), reversed(self._traces), strict=True):
|
||||
numbering = range(len(self._traces), 0, -1)
|
||||
for number, trace in zip(numbering, reversed(self._traces), strict=True):
|
||||
rows.append(self._trace_row(number, trace))
|
||||
return Group(*rows)
|
||||
|
||||
|
||||
@@ -346,7 +346,8 @@ class TextScreen(_KeylessDialog):
|
||||
if self._byte_limit is not None:
|
||||
over = self._used_bytes() - self._byte_limit
|
||||
if over > 0:
|
||||
self._error = f"Too long by {over} byte{'s' if over != 1 else ''} — trim to send."
|
||||
plural = "s" if over != 1 else ""
|
||||
self._error = f"Too long by {over} byte{plural} — trim to send."
|
||||
return
|
||||
if self._validate is not None:
|
||||
result = self._validate(value)
|
||||
|
||||
@@ -29,8 +29,9 @@ only its immediate neighbourhood:
|
||||
* the **link list** beneath names every way onward as a selectable row, strongest
|
||||
observed link first — so the cursor's home, row 0, is always the strongest link out of
|
||||
here: type glyph, name, hash, SNR with a quality bar, the evidence behind the link
|
||||
(samples, sources, age), and how many links continue onward from that node. The highlighted row's marker and label light white on the canvas. The
|
||||
list scrolls *within* the screen — the canvas, legend, and heading hold still, and
|
||||
(samples, sources, age), and how many links continue onward from that node. The highlighted
|
||||
row's marker and label light white on the canvas. The list scrolls *within* the screen — the
|
||||
canvas, legend, and heading hold still, and
|
||||
faint ``↑/↓ n more`` markers bracket the window — with ↑↓ moving one row and
|
||||
PgUp/PgDn a windowful.
|
||||
|
||||
|
||||
@@ -1347,7 +1347,9 @@ def tx_opt_summary(result: TxOptResult) -> Panel:
|
||||
(f"{result.original_tx if result.original_tx is not None else '?'}\n", ""),
|
||||
("status ", "muted"), Text.from_markup(applied),
|
||||
)
|
||||
return Panel(body, title="[accent]TX optimization[/accent]", border_style="accent", expand=False)
|
||||
return Panel(
|
||||
body, title="[accent]TX optimization[/accent]", border_style="accent", expand=False
|
||||
)
|
||||
|
||||
|
||||
# -- battery gauge --------------------------------------------------------------------------
|
||||
|
||||
@@ -109,9 +109,10 @@ def check_drive_is_ours(drive, force=False):
|
||||
if "6." in softdev:
|
||||
problems.append(f"{softdev} expects the app at 0x26000; this build is linked for S140 v7")
|
||||
if problems and not force:
|
||||
listed = "\n - ".join(problems)
|
||||
sys.exit(
|
||||
"ERROR: refusing to flash {} --\n - {}\n"
|
||||
"Unplug the other board, or re-run with --force if you are sure.".format(drive, "\n - ".join(problems))
|
||||
f"ERROR: refusing to flash {drive} --\n - {listed}\n"
|
||||
"Unplug the other board, or re-run with --force if you are sure."
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
+3
-2
@@ -4,8 +4,9 @@ The pages carry no state and no controls, so what is worth pinning is what they
|
||||
and how they are framed: the live package facts they must never drift from, the
|
||||
indent block their prose hangs in, the derived footer that only names the pager when
|
||||
there is something to page, the landmarks their sections leave for the sticky heading
|
||||
and the section jump, and the section they hang under in the main menu. How markdown itself is drawn is ``test_markdown``'s subject; width discipline on
|
||||
both platforms is the gallery's (``test_gallery``).
|
||||
and the section jump, and the section they hang under in the main menu. How markdown
|
||||
itself is drawn is ``test_markdown``'s subject; width discipline on both platforms is
|
||||
the gallery's (``test_gallery``).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
+10
-4
@@ -87,7 +87,10 @@ def test_battery_cell_colours_by_band_while_the_dots_carry_the_detail() -> None:
|
||||
assert _glyph_style(battery_cell(7)) == "batt.low"
|
||||
# The band holds across a band's worth of rungs, so only the fill moves inside it.
|
||||
# (Below 6.25% the alarm takes the cell over, which the next test covers.)
|
||||
turns = [p for p in range(8, 101) if _glyph_style(battery_cell(p)) != _glyph_style(battery_cell(p - 1))]
|
||||
turns = [
|
||||
p for p in range(8, 101)
|
||||
if _glyph_style(battery_cell(p)) != _glyph_style(battery_cell(p - 1))
|
||||
]
|
||||
assert turns == [25, 50]
|
||||
assert len({battery_cell(p).plain[0] for p in range(25, 50)}) == 2 # two rungs per band
|
||||
|
||||
@@ -115,9 +118,12 @@ def test_battery_cell_charging_sweeps_bottom_to_full_holding_the_percent() -> No
|
||||
|
||||
def test_battery_cell_charging_keeps_the_bands_colour_while_the_fill_sweeps() -> None:
|
||||
"""The sweep is an animation, not a reading: colour still answers for the real charge."""
|
||||
assert {_glyph_style(battery_cell(20, charging=True, frame=f)) for f in range(5)} == {"batt.low"}
|
||||
assert {_glyph_style(battery_cell(40, charging=True, frame=f)) for f in range(5)} == {"batt.mid"}
|
||||
assert {_glyph_style(battery_cell(90, charging=True, frame=f)) for f in range(5)} == {"batt.full"}
|
||||
charging = {_glyph_style(battery_cell(20, charging=True, frame=f)) for f in range(5)}
|
||||
assert charging == {"batt.low"}
|
||||
charging = {_glyph_style(battery_cell(40, charging=True, frame=f)) for f in range(5)}
|
||||
assert charging == {"batt.mid"}
|
||||
charging = {_glyph_style(battery_cell(90, charging=True, frame=f)) for f in range(5)}
|
||||
assert charging == {"batt.full"}
|
||||
|
||||
|
||||
def test_battery_cell_charging_never_alarms_however_empty_the_pack_is() -> None:
|
||||
|
||||
+10
-3
@@ -193,7 +193,8 @@ def test_decrypt_channel_text_rejects_a_fingerprint_collision() -> None:
|
||||
def test_decrypt_channel_text_rejects_malformed_hex() -> None:
|
||||
"""Garbage hex fields fail closed instead of raising."""
|
||||
assert decrypt_channel_text("zz", "zzzz", "zzzzzzzzzzzzzzzz", [("x", random_secret())]) is None
|
||||
assert decrypt_channel_text("00", "0000", "00" * 15, [("x", random_secret())]) is None # not block-aligned
|
||||
# a 15-byte body is not block-aligned
|
||||
assert decrypt_channel_text("00", "0000", "00" * 15, [("x", random_secret())]) is None
|
||||
|
||||
|
||||
# -- QR rendering -------------------------------------------------------------
|
||||
@@ -374,10 +375,16 @@ class _ScriptedUi:
|
||||
async def select(self, title: str, items: list, *, default=None): # noqa: ANN001, ANN201
|
||||
return self._selects.pop(0) if self._selects else "__back__"
|
||||
|
||||
async def text(self, title: str, *, default: str = "", validate=None, help_text: str = "", password: bool = False): # noqa: ANN001, ANN201
|
||||
async def text( # noqa: ANN001, ANN201
|
||||
self, title: str, *, default: str = "", validate=None,
|
||||
help_text: str = "", password: bool = False,
|
||||
):
|
||||
return self._texts.pop(0)
|
||||
|
||||
async def dialog(self, prompt, buttons, *, title: str = "", default: int = 0, keys=None, danger: bool = False, destructive: bool = False): # noqa: ANN001, ANN201
|
||||
async def dialog( # noqa: ANN001, ANN201
|
||||
self, prompt, buttons, *, title: str = "", default: int = 0, keys=None,
|
||||
danger: bool = False, destructive: bool = False,
|
||||
):
|
||||
return self._dialogs.pop(0)
|
||||
|
||||
|
||||
|
||||
+7
-3
@@ -484,7 +484,9 @@ def test_title_preview_column_aligns_regardless_of_label_length(repo: Repository
|
||||
"""The preview starts at the same column whether the label is short or (clipped) long."""
|
||||
ctx = _RowCtx(repo)
|
||||
short = Conversation(label="A", is_channel=True, channel_idx=0, channel_id="c0")
|
||||
long = Conversation(label="A much longer channel name here", is_channel=True, channel_idx=1, channel_id="c1")
|
||||
long = Conversation(
|
||||
label="A much longer channel name here", is_channel=True, channel_idx=1, channel_id="c1"
|
||||
)
|
||||
m0 = ChatMessage(text="X: hello", is_channel=True, channel_id="c0")
|
||||
m1 = ChatMessage(text="Y: hello", is_channel=True, channel_id="c1")
|
||||
l0 = _title(ctx, short, {"chan:c0": m0}, _NO_KEYS).plain
|
||||
@@ -493,7 +495,7 @@ def test_title_preview_column_aligns_regardless_of_label_length(repo: Repository
|
||||
|
||||
|
||||
def test_title_leads_with_openness_glyph(repo: Repository) -> None:
|
||||
"""Channel rows lead with an openness glyph: # name-derived, 🌐 fixed-key public, 🔒 private."""
|
||||
"""Channel rows lead with an openness glyph: # name-derived, 🌐 public, 🔒 private."""
|
||||
ctx = _RowCtx(repo)
|
||||
def head(conv):
|
||||
return _title(ctx, conv, {}, _NO_KEYS).plain.split(" ", 1)[0]
|
||||
@@ -1140,7 +1142,9 @@ def test_channel_transcript_groups_by_sender() -> None:
|
||||
ChatMessage(text="Alice: hi", is_channel=True, channel_idx=0, created_at=at(0)),
|
||||
ChatMessage(text="Alice: again", is_channel=True, channel_idx=0, created_at=at(6)),
|
||||
ChatMessage(text="Bob: yo", is_channel=True, channel_idx=0, created_at=at(8)),
|
||||
ChatMessage(text="hello all", outbound=True, is_channel=True, channel_idx=0, created_at=at(9)),
|
||||
ChatMessage(
|
||||
text="hello all", outbound=True, is_channel=True, channel_idx=0, created_at=at(9)
|
||||
),
|
||||
]
|
||||
screen = ChatScreen(conv, messages, send=None, names={}, session=_StubSession())
|
||||
rendered = _strip_ansi("\n".join(screen._render_grouped(80)))
|
||||
|
||||
@@ -296,7 +296,8 @@ def test_recent_observations_rehydrate_the_packet_payload_class(tmp_path: Path)
|
||||
)
|
||||
repo.record_observation(run, _obs(node="a1b2", kind="advert")) # a plain advert nearby
|
||||
|
||||
window = {o.node or "": o for o in repo.recent_observations(since=utcnow() - timedelta(hours=2))}
|
||||
recent = repo.recent_observations(since=utcnow() - timedelta(hours=2))
|
||||
window = {o.node or "": o for o in recent}
|
||||
assert window[""].raw is not None and window[""].raw["payload_typename"] == "TRACE"
|
||||
assert window["a1b2"].raw is None # an advert keeps no packet raw, as before
|
||||
repo.close()
|
||||
|
||||
@@ -115,7 +115,8 @@ def _contacts_names(counts, sort: str, prefix_bytes: int = 3):
|
||||
Contact(name="Alice", public_key="3d63c6" + "00" * 29, key_prefix="3d63c6"),
|
||||
]
|
||||
group = contacts_table(
|
||||
"Homestead", "aabbcc" + "00" * 29, contacts, prefix_bytes, counts, ContactsSort.from_name(sort)
|
||||
"Homestead", "aabbcc" + "00" * 29, contacts, prefix_bytes, counts,
|
||||
ContactsSort.from_name(sort),
|
||||
)
|
||||
table = group.renderables[0] # (table, blank line, legend)
|
||||
names = [c.plain if hasattr(c, "plain") else str(c) for c in table.columns[1].cells]
|
||||
|
||||
@@ -33,7 +33,8 @@ class FakeDevice:
|
||||
self.contacts_calls += 1
|
||||
if self.contact_rows is not None:
|
||||
return list(self.contact_rows)
|
||||
return [Contact(name=f"contact-{self.contacts_calls}")] # fresh identity per fetch, to spot refreshes
|
||||
# a fresh identity per fetch, so a re-read is visible
|
||||
return [Contact(name=f"contact-{self.contacts_calls}")]
|
||||
|
||||
async def get_self_info(self) -> dict:
|
||||
self.self_info_calls += 1
|
||||
|
||||
@@ -130,7 +130,8 @@ def test_bare_text_default_emoji_are_left_exactly_as_measured() -> None:
|
||||
for bare in (_ROAD, _WEB):
|
||||
assert cell_len(bare) == 1 and cache[bare] == 1
|
||||
# A heading built around one costs its own cells and no more, so the row frames flush.
|
||||
assert cell_len(f"── {_ROAD} Longest distance ──") == len("── ") + 1 + len(" Longest distance ──")
|
||||
caption = cell_len(f"── {_ROAD} Longest distance ──")
|
||||
assert caption == len("── ") + 1 + len(" Longest distance ──")
|
||||
|
||||
|
||||
def test_flags_measure_two_cells_in_both_authorities() -> None:
|
||||
|
||||
@@ -302,7 +302,9 @@ def _chat(cols: int, rows: int) -> Screen:
|
||||
)
|
||||
messages = [
|
||||
ChatMessage(text="on my way, should be there soon", outbound=False, peer="d4e5f6a7"),
|
||||
ChatMessage(text="sounds good, see you shortly", outbound=True, peer="d4e5f6a7", acked=True),
|
||||
ChatMessage(
|
||||
text="sounds good, see you shortly", outbound=True, peer="d4e5f6a7", acked=True
|
||||
),
|
||||
]
|
||||
return ChatScreen(
|
||||
conv, messages, send=None, names={"d4e5f6a7": "Alice"}, session=_GallerySession(cols, rows),
|
||||
|
||||
+10
-3
@@ -112,7 +112,8 @@ def test_decode_tile_filter_skips_the_layers_the_map_never_draws() -> None:
|
||||
"""The layers left undecoded are the ones that made a tile expensive."""
|
||||
from meshterm.ui.map_render import DRAWN_LAYERS
|
||||
|
||||
narrow = {layer.name: layer for layer in decode_tile(_FIXTURE.read_bytes(), layers=DRAWN_LAYERS)}
|
||||
drawn = decode_tile(_FIXTURE.read_bytes(), layers=DRAWN_LAYERS)
|
||||
narrow = {layer.name: layer for layer in drawn}
|
||||
for skipped in ("housenumber", "poi", "mountain_peak"):
|
||||
assert skipped in narrow, f"{skipped} should still be named"
|
||||
assert not narrow[skipped].features, f"{skipped} should not have been decoded"
|
||||
@@ -905,7 +906,10 @@ def test_decoded_layers_survive_a_round_trip() -> None:
|
||||
restored = loads_layers(dumps_layers(original, stamp="x"), stamp="x")
|
||||
|
||||
assert restored is not None
|
||||
assert [(layer.name, layer.extent) for layer in restored] == [(layer.name, layer.extent) for layer in original]
|
||||
def shape(layers):
|
||||
return [(layer.name, layer.extent) for layer in layers]
|
||||
|
||||
assert shape(restored) == shape(original)
|
||||
for before, after in zip(original, restored, strict=True):
|
||||
assert after.features == before.features
|
||||
|
||||
@@ -2282,7 +2286,10 @@ def test_map_paints_without_waiting_for_the_ground(monkeypatch) -> None: # noqa
|
||||
|
||||
calls: list[tuple] = []
|
||||
real = ms.render_map
|
||||
monkeypatch.setattr(ms, "render_map", lambda vp, tiles, m, **k: calls.append(tiles) or real(vp, tiles, m, **k))
|
||||
monkeypatch.setattr(
|
||||
ms, "render_map",
|
||||
lambda vp, tiles, m, **k: calls.append(tiles) or real(vp, tiles, m, **k),
|
||||
)
|
||||
started: list = []
|
||||
screen = _async_map(monkeypatch, started)
|
||||
|
||||
|
||||
@@ -60,9 +60,17 @@ def test_channel_arrivals_match_by_decrypted_content(tmp_path: Path) -> None:
|
||||
repo, run = _repo(tmp_path)
|
||||
now = utcnow()
|
||||
wire = "Alice: hi mesh"
|
||||
_record_frame(repo, run, when=now - timedelta(seconds=5), path="3d63", raw=_grp_txt_raw(SECRET, wire))
|
||||
_record_frame(repo, run, when=now - timedelta(seconds=3), path="3d63,a1b2", raw=_grp_txt_raw(SECRET, wire))
|
||||
_record_frame(repo, run, when=now - timedelta(seconds=1), path="", raw=_grp_txt_raw(SECRET, "Alice: other"))
|
||||
_record_frame(
|
||||
repo, run, when=now - timedelta(seconds=5), path="3d63", raw=_grp_txt_raw(SECRET, wire)
|
||||
)
|
||||
_record_frame(
|
||||
repo, run, when=now - timedelta(seconds=3), path="3d63,a1b2",
|
||||
raw=_grp_txt_raw(SECRET, wire),
|
||||
)
|
||||
_record_frame(
|
||||
repo, run, when=now - timedelta(seconds=1), path="",
|
||||
raw=_grp_txt_raw(SECRET, "Alice: other"),
|
||||
)
|
||||
|
||||
message = ChatMessage(text=wire, is_channel=True, created_at=now)
|
||||
arrivals = channel_arrivals(repo, message, channel_name="#general", secret=SECRET)
|
||||
|
||||
@@ -247,7 +247,7 @@ def test_route_line_hash_width_follows_our_path_hash_mode() -> None:
|
||||
|
||||
|
||||
def test_route_line_marks_the_best_route_and_its_context() -> None:
|
||||
"""The winner wears ★ best and trails its bottleneck SNR and sample count, on the context line."""
|
||||
"""The winner wears ★ best and trails its bottleneck SNR and sample count on the context row."""
|
||||
_path, context = _route_line(
|
||||
"Far", FAR.public_key, ("3d63c6429436",), "best", 6.5, 4,
|
||||
resolve=make_node_resolver([HUB]), node_known=True,
|
||||
@@ -301,7 +301,7 @@ def _view(topo, suggested, device_route, target, node_label, contacts, name_key=
|
||||
|
||||
|
||||
def test_routes_view_draws_evidence_and_notes_its_absence() -> None:
|
||||
"""With route evidence the view carries selectable routes; without any, a muted note stands in."""
|
||||
"""With route evidence the view carries selectable routes; without, a muted note stands in."""
|
||||
topo = _topo_with_route()
|
||||
view = _view(topo, topo.suggested("f2c24f54551e"), ("3d63c6429436",),
|
||||
"f2c24f54551e", "Far", [HUB, FAR])
|
||||
@@ -322,7 +322,7 @@ def test_routes_view_draws_evidence_and_notes_its_absence() -> None:
|
||||
|
||||
|
||||
def test_routes_view_puts_the_contact_on_the_left_and_us_on_the_right() -> None:
|
||||
"""The graph reads node → us (the inbound direction): the contact on the left, our star right."""
|
||||
"""The graph reads node → us (the inbound direction): the contact left, our star right."""
|
||||
topo = _topo_with_route()
|
||||
view = _view(topo, topo.suggested("f2c24f54551e"), ("3d63c6429436",),
|
||||
"f2c24f54551e", "Far", [HUB, FAR])
|
||||
@@ -422,7 +422,7 @@ def test_good_alternatives_keeps_observed_routes_and_drops_outliers() -> None:
|
||||
|
||||
|
||||
def test_route_freshness_drops_a_route_with_a_long_quiet_hop() -> None:
|
||||
"""A route counts as stale — and is dropped — once its stalest hop goes quiet past the horizon."""
|
||||
"""A route counts as stale — and is dropped — once its stalest hop falls past the horizon."""
|
||||
from datetime import timedelta
|
||||
|
||||
from meshterm.persistence.repository import TracedPath
|
||||
@@ -687,8 +687,14 @@ def test_node_detail_routes_stage_spends_the_caption_and_top_air_on_the_fan() ->
|
||||
screen.handle("tab") # onto Routes
|
||||
screen.note_viewport(viewport)
|
||||
lines = screen.render_body(49)
|
||||
strip_idx = next(i for i, ln in enumerate(lines) if "Routes" in _plain([ln]) and "│" in _plain([ln]))
|
||||
rule_idx = next(i for i, ln in enumerate(lines) if set(_plain([ln])) == {"─"} and i > strip_idx)
|
||||
def is_strip(ln):
|
||||
return "Routes" in _plain([ln]) and "│" in _plain([ln])
|
||||
|
||||
strip_idx = next(i for i, ln in enumerate(lines) if is_strip(ln))
|
||||
rule_idx = next(
|
||||
i for i, ln in enumerate(lines)
|
||||
if set(_plain([ln])) == {"─"} and i > strip_idx
|
||||
)
|
||||
return [_plain([ln]) for ln in lines[strip_idx + 2 : rule_idx]] # past the strip's own rule
|
||||
|
||||
set_platform(PICOCALC)
|
||||
@@ -976,7 +982,10 @@ def test_route_labels_light_through_a_coalesced_hop() -> None:
|
||||
|
||||
routes = _RoutesView(
|
||||
routes=[
|
||||
_Route(draw=("bf61f2fb1d9e", "3d63c6429436"), spec="s0", path=Text("wide"), context=Text("")),
|
||||
_Route(
|
||||
draw=("bf61f2fb1d9e", "3d63c6429436"), spec="s0",
|
||||
path=Text("wide"), context=Text(""),
|
||||
),
|
||||
_Route(draw=("bf61f2fb1d9e", "3d"), spec="s1", path=Text("short"), context=Text("")),
|
||||
],
|
||||
glyph_of=lambda n: ("●", "#ffffff"),
|
||||
@@ -996,7 +1005,10 @@ def test_node_detail_route_list_windows_inside_the_page() -> None:
|
||||
"""
|
||||
routes = _RoutesView(
|
||||
routes=[
|
||||
_Route(draw=(f"{i:x}{i:x}" * 6,), spec=f"s{i}", path=Text(f"route {i}"), context=Text(""))
|
||||
_Route(
|
||||
draw=(f"{i:x}{i:x}" * 6,), spec=f"s{i}",
|
||||
path=Text(f"route {i}"), context=Text(""),
|
||||
)
|
||||
for i in range(12)
|
||||
],
|
||||
glyph_of=lambda n: ("●", "#ffffff"),
|
||||
@@ -1026,7 +1038,10 @@ def test_node_detail_route_cursor_clamps_at_both_ends() -> None:
|
||||
"""
|
||||
routes = _RoutesView(
|
||||
routes=[
|
||||
_Route(draw=(f"{i:x}{i:x}" * 6,), spec=f"s{i}", path=Text(f"route {i}"), context=Text(""))
|
||||
_Route(
|
||||
draw=(f"{i:x}{i:x}" * 6,), spec=f"s{i}",
|
||||
path=Text(f"route {i}"), context=Text(""),
|
||||
)
|
||||
for i in range(12)
|
||||
],
|
||||
glyph_of=lambda n: ("●", "#ffffff"),
|
||||
|
||||
@@ -313,8 +313,9 @@ def test_a_shared_relay_draws_a_single_marker() -> None:
|
||||
|
||||
|
||||
def _marker_rows(lines, glyph): # noqa: ANN001, ANN202
|
||||
"""The distinct canvas row indices a marker glyph is drawn on (endpoints ``★``, relays ``●``)."""
|
||||
return sorted({i for i, ln in enumerate(_ANSI.sub("", "\n".join(lines)).splitlines()) if glyph in ln})
|
||||
"""The distinct canvas rows a marker glyph is drawn on (endpoints ``★``, relays ``●``)."""
|
||||
plain = _ANSI.sub("", "\n".join(lines)).splitlines()
|
||||
return sorted({i for i, ln in enumerate(plain) if glyph in ln})
|
||||
|
||||
|
||||
def test_even_lane_count_centres_the_endpoints() -> None:
|
||||
|
||||
@@ -143,12 +143,21 @@ def test_hourly_series_groups_by_clock_hour(tmp_path: Path) -> None:
|
||||
base = now.replace(minute=0, second=0, microsecond=0) # top of the current UTC hour
|
||||
three_h = base - timedelta(hours=3) # top of a busy hour, three back
|
||||
for node in ("aa" * 6, "bb" * 6):
|
||||
repo.record_observation(run, Observation(node=node, observed_at=three_h + timedelta(minutes=5)))
|
||||
repo.record_observation(run, Observation(node="aa" * 6, observed_at=three_h + timedelta(minutes=20)))
|
||||
repo.record_observation(
|
||||
run, Observation(node=node, observed_at=three_h + timedelta(minutes=5))
|
||||
)
|
||||
repo.record_observation(
|
||||
run, Observation(node="cc" * 6, kind="packet", path="", observed_at=three_h + timedelta(minutes=40))
|
||||
run, Observation(node="aa" * 6, observed_at=three_h + timedelta(minutes=20))
|
||||
)
|
||||
repo.record_observation(
|
||||
run,
|
||||
Observation(
|
||||
node="cc" * 6, kind="packet", path="", observed_at=three_h + timedelta(minutes=40)
|
||||
),
|
||||
)
|
||||
repo.record_observation(
|
||||
run, Observation(node="aa" * 6, observed_at=base + timedelta(minutes=1))
|
||||
)
|
||||
repo.record_observation(run, Observation(node="aa" * 6, observed_at=base + timedelta(minutes=1)))
|
||||
series = repo.hourly_series(now - timedelta(days=1))
|
||||
by_hour = {iso: (pkts, nodes) for iso, pkts, nodes in series}
|
||||
# Keys are local wall-clock hours, so form the expected keys via the same rotation.
|
||||
|
||||
+3
-2
@@ -168,8 +168,9 @@ def test_walk_selected_link_lights_the_route_that_reaches_it() -> None:
|
||||
topo = MeshTopology(US, contacts=[hub, alice, bob])
|
||||
y = topo.canonical(hub.public_key)
|
||||
topo.add_walk([topo.self_id, y], snrs=[10.0], when=stale, source="trace") # approach: green
|
||||
topo.add_walk([y, topo.canonical(alice.public_key)], snrs=[0.0], when=stale, source="trace") # amber
|
||||
topo.add_walk([y, topo.canonical(bob.public_key)], snrs=[-15.0], when=stale, source="trace") # red
|
||||
# amber, then red
|
||||
topo.add_walk([y, topo.canonical(alice.public_key)], snrs=[0.0], when=stale, source="trace")
|
||||
topo.add_walk([y, topo.canonical(bob.public_key)], snrs=[-15.0], when=stale, source="trace")
|
||||
screen = WalkScreen(
|
||||
session=_FakeSession(),
|
||||
topo=topo,
|
||||
|
||||
Reference in new issue
Block a user