13 Commits
Author SHA1 Message Date
Jean-Pierre MartineauandClaude Opus 5.5 11b5274b31 Docstrings and comments of the tests are in ASD-STE100
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>
2026-10-06 21:36:50 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 b6e7cd5c16 The handhelds are named for their variant: picocalc-lyra and cardputer-zero
M5Stack makes three Cardputers and only the Zero runs Linux; the PicoCalc
takes several cores and only the Luckfox Lyra runs MeshTerm. So the bare
names were ambiguous, and every reference to the two devices now carries
the variant: the --platform values and MESHTERM_PLATFORM, the Platform
constants (PICOCALC_LYRA, CARDPUTER_ZERO), their lane decks and every
screen's picocalc_lyra_lane / cardputer_zero_lane, the font inventories,
the lane chips' theme styles, the scripts/ folders, the two chrome test
files, and the docs and CLAUDE.md that name them.

There is no alias: --platform picocalc is refused with the three real
choices listed, rather than falling back to the desktop layout. The
PicoCalc is auto-detected from its device tree, so it needs nothing.
What names hardware or a user's data keeps its name: the PicoCalc
keyboard's input-device name, its battery's sysfs supply, and the serial
profile the Lyra setup script writes. .gitignore follows the firmware's
folder, and the changelog keeps the words each release used.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 11:52:41 -04:00
Jean-Pierre MartineauandClaude Opus 5.5 6eb7d8f6bd Each handheld deals its own F-key lane, and the Cardputer Zero joins as a platform
A lane's keyboard-bound half is now a LaneDeck named by Platform.lane_deck: which
keycodes drive the slots, where the chips sit, how they are drawn, and which of a
screen's lane definitions it reads. Screens define picocalc_lane; cardputer_lane
follows it until a screen overrides it, so the two can part ways one screen at a
time. The Cardputer deck is Fn+4..8, Shift with them as F16-F20, eight-cell chips
centred over their keys in the fn key's orange-red and the Shift blue.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 19:33:44 -04:00
Jean-Pierre MartineauandClaude Fable 5.1 c9f5a71ce7 Every Python file names its licence with an SPDX identifier
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>
2026-09-13 13:09:18 -04:00
Jean-Pierre MartineauandClaude Opus 5 40ecf5151d The tests' docstrings lead with a sentence too
The last 122 `D205` sites, which takes `ruff check .` to zero.

Same shape as the package pass and just as additive: these were a paragraph
with no summary, so each one gains a plain sentence saying what is being
asserted, and every word that was there moves down into the description. The
dense ones benefit most — `test_pathline`'s 23, where "A seam is one cell, not
two" now stands alone and the chevron mechanics explain it underneath, and
`test_tui`'s wide-glyph repaint test, whose seven lines on prompt_toolkit's
relative cursor now sit under the one line that says what the test pins.

Four module docstrings (`test_advert`, `test_gallery`, `test_platforms`,
`test_repeater_admin`) opened with "X tests: a, b, and c" and now name the
subject on the first line with the list below it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 20:37:51 -04:00
Jean-Pierre MartineauandClaude Opus 5 b5006cc415 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>
2026-09-06 20:22:52 -04:00
Jean-Pierre MartineauandClaude Opus 5 71b16ec7b7 The annotations, imports, and dead locals answer to ruff
`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>
2026-09-06 17:26:12 -04:00
Jean-Pierre MartineauandClaude Opus 5 34092613f1 The tests read the About pages as written, not as scaffolded
The four pages were scaffolds when their test was written: three headings apiece
standing in for a shape not yet settled, and under each one a muted placeholder
line in the app's empty-state voice. All four have been written since, and six
assertions were still describing the scaffold -- a suite that fails on the work
being finished is a suite nobody can read a real regression out of.

The headings are now one list, PAGE_HEADINGS, because two tests ask two things
of the same fact -- that they render in order, and that each is a stop the sticky
heading can pin -- and a page that grows a section should not be able to satisfy
one while drifting from the other. That list is the pages as they stand: the
author page's four, and the platform and future sections About MeshTerm gained.

The placeholder test loses its subject entirely, so it goes. Its neighbour, the
one holding prose to its indent block, was *passing* on the same absence -- it
looked for placeholder lines that had escaped to column 0 and found no
placeholders at all. It now asks the question directly: on the support page,
which carries neither of the two blocks that are page frame rather than body,
nothing but a heading may sit flush.

The licence check still wanted MIT. It reads Apache 2.0 now, and also holds the
page to the trademark sentence -- the part that says what the licence does not
cover, which is the half a fork most needs to have been told.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 00:10:31 -04:00
Jean-Pierre MartineauandClaude Opus 5 6fc4e7afc9 The community has a page, and the page is its invite
A fourth About page, `Join Discord`, sitting between the author and the ask:
what is this, who made it, where is everyone, how do I help. Like its three
siblings it is written markdown under assets/pages, reads nothing and
transmits nothing, and keeps a CLI face — `meshterm discord`.

It is short on purpose. The invite link, and the code for it in a `qr` fence,
because on a PicoCalc there is no clipboard and the code is how a link leaves
the screen at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 08:38:44 -04:00
Jean-Pierre MartineauandClaude Opus 5 ea9b7c1cb1 Main menu: Device config, Support MeshTerm
Two rows renamed, and the rename carried into the screens they open —
the config editor's table title and its staged-count header, the support
page's own title (it comes from the tool, so it followed on its own).

"Support this project" never said which project; the menu is MeshTerm's
and the section above it is already About MeshTerm, so the name is the
useful half of that phrase. "Device configuration" was the longest label
in the list and set the width of the whole name column: at 19 cells
instead of 23, every description now gets 51 and the widest row is 68.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 17:15:00 -04:00
Jean-Pierre MartineauandClaude Opus 5 9f5d337cae About MeshTerm: the page says something now
The three sections were placeholders holding a shape. This writes the
first one: what MeshTerm is (one screen for what the radio can tell you,
the five menu sections as a list, and the standing promise that nothing
transmits unasked), where it came from (June 2026, a few serial scripts,
grown by the recording; the mesh's scarce airtime is why it listens more
than it speaks; later the PicoCalc), and the licence — MIT, as the
package metadata declares, with the credit MeshCore and OpenStreetMap
are owed, the ODbL by name.

The layout is unchanged: title, standfirst, three sections, rule,
colophon. It just runs longer than a screenful now, which is what the
sticky headings and the section jumps were for.

Link targets also drop a leading www., so the OSM copyright URL reads as
the host it is rather than a subdomain nobody types.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 17:55:41 -04:00
Jean-Pierre MartineauandClaude Opus 5 0314ed7702 About pages: prose the app writes, not rows it composes
The three About pages were scaffolding built out of Text() calls, which
made writing them a Python edit and capped what they could ever say. They
are markdown now — meshterm/assets/pages/*.md, drawn by ui/markdown.py
into the app's own language rather than markdown's: # the page's name in
brand, ## a section in the body accent (with the muted · aside), ###
indented with its prose, paragraphs and lists hanging as blocks, quotes
and fences behind a rail that survives wrapping, tables through Rich, and
links that say where they go — nothing is clickable on a framebuffer
console. Two paragraphs are page frame rather than body and sit flush and
muted: the standfirst under the title, the colophon under the closing
rule, which is where the copyright lands.

The pages stay scaffolded on purpose — the placeholders are unchanged —
but filling one in is now editing its .md, and the live package facts
arrive as {version}/{author}/{copyright} so a page still can't drift from
the build it describes.

Being a document earns the screen two things a block never had: its ##
headings are landmarks, so a heading pins while its own prose scrolls
under it and ^PgUp/^PgDn step by them, and the console lane claims
Sect ↑/Sect ↓ on the free F1/F2 pair once a page has more than one
section — dimmed while the whole page fits, since the lane never
advertises a dead key.

Parsing is markdown-it-py (already under Rich, now declared): a real
CommonMark parser, so an author writes ordinary markdown. The md.* styles
are defined in both themes; the new test renders the whole vocabulary at
53 cells and asserts it stays inside the console font and the 16 slots.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 17:39:21 -04:00
Jean-Pierre MartineauandClaude Opus 5 3ccffd13c9 About MeshTerm: a sixth section, for the questions the five doings can't hold
The menu asks "What would you like to do?" and its five sections each
name a doing. "What is this thing, and who made it" is not one, and had
nowhere to go — so it gets the sixth section, the one that names a
subject, sorted last where a reader who has run out of things to do finds
it. Three pages, in the order a stranger asks:

  About MeshTerm        what this is, where it came from, its terms
  About the author      the person behind it, on the mesh and off it
  Support this project  what keeps it going, and the ways to help

Every page is scaffolding: the headings are the settled part, and each
section carries one lowercase muted "placeholder — …" line where its
prose will go. That is the app's own empty-state voice, so an unwritten
page reads as deliberately unwritten rather than as a screen that failed
to load, and filling one in means replacing its placeholder — the screen,
the menu rows and the CLI faces all follow unchanged. What is not a
placeholder is the version, the author and the copyright span: they come
from the package itself, so the About page can never describe a build it
isn't running inside.

The pages act on nothing — no radio, no database, no device — so each
opens straight to its screen with no prompt first, and each keeps a CLI
face (meshterm about / about-author / support) printing the same page to
a shell.

Two new icons, both funnelled for the console: 📖 read/about becomes ¶,
the page-of-prose mark, and 💰 support/donate becomes $.

ScrollScreen earns a derived footer hint on the way through. A read-only
body has only two keys to name, the pager and Esc, so passing "↑↓ PgUp/
PgDn scroll · Esc close" at every call site was both repetition and a lie
on a body that fits — the standing rule is that a footer never advertises
a key that would do nothing. Left unset, it now names the pager only
while the content actually overflows, and takes its Esc verb from the
surface: a floating view closes, a full screen goes back. Every existing
caller passes its hint explicitly and is untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 16:55:46 -04:00