Files
MeshTerm/meshterm/ui/specimen.py
T
Jean-Pierre MartineauandClaude Opus 5 92d31a3191 Every file is spelled the way the formatter spells it
`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>
2026-09-07 01:07:19 -04:00

175 lines
7.0 KiB
Python

"""The visual-language specimen: every mark, icon, colour and fold on one screen.
``meshterm specimen`` prints this through the platform's real machinery — the active
theme, the ``glyph()`` icon funnel, the render-boundary fold, the heat and SNR scales —
so what it shows *is* what the platform draws, not a mock-up of it. On the PicoCalc
console it doubles as the font/palette acceptance card (all nine P3 marks, the node
glyphs, braille, the 16 programmed slots); on a desktop terminal it shows the same
content in the regular platform's emoji-and-truecolor dress. Sized to the PicoCalc
floor: every line ≤ 53 cells, the whole card ≤ 24 rows.
"""
from __future__ import annotations
from rich.console import RenderableType
from rich.text import Text
from ..platforms import get_platform
from .packet_viewer import KIND_ICONS, PAYLOAD_ICONS
from .theme import _VT_SLOTS, fold_text, glyph, name_style, snr_style
from .tui.fkeys import FPair, lane_text
from .widgets import _recency_style, channel_glyph, name_chip
#: Demo identities for the name-colour row: ``(name, key)``. The keys are synthetic, and
#: chosen so their first bytes land in three different sectors of the hue wheel — on the
#: console that means three different palette slots (see ``theme._NODE_SLOT_HEXES``).
_DEMO_NAMES = (("Alice", "a1b2"), ("Lakeside", "3d63"), ("Lounge", "cc10"))
def _palette_row() -> Text:
"""The 16 slots as filled blocks, dim bank then bright bank.
On PicoCalc the blocks address the console's real slots (``color(N)``), so the row
shows exactly what ``setvtrgb`` programmed; on the regular platform they carry the
design's RGB values instead — a preview of the same palette in truecolor.
"""
row = Text()
for slot, _, hex_ in _VT_SLOTS:
style = f"color({slot})" if not get_platform().truecolor else hex_
row.append("██", style=style)
return row
def _ages_row() -> Text:
"""The heat scale over its own anchors — one age just inside each of the seven steps."""
row = Text()
anchors = (
("now", 0),
("20m", 1200),
("3h", 10800),
("2d", 172800),
("2w", 1209600),
("3mo", 7776000),
("never", None),
)
for label, secs in anchors:
if row.plain:
row.append(" ")
row.append(label, style=_recency_style(secs))
return row
def specimen_lines() -> list[RenderableType]:
"""The specimen card, one renderable per line (print through the themed console)."""
platform = get_platform()
lines: list[RenderableType] = []
lines.append(Text.assemble(("Specimen", "accent"), (" · ", "muted"), platform.name))
lines.append(Text())
lines.append(_palette_row())
legend = Text("stock palette · 8 muted · 9-14 node hues · 15 you", style="muted")
lines.append(legend)
lines.append(Text())
status = Text("status ")
for mark, style in (("✓", "ok"), ("✗", "err"), ("⚠", "warn"), ("●", "err"), ("○", "muted")):
status.append(mark + " ", style=style)
lines.append(status)
# The node-type marks in the theme's own ``type.*`` entries — the same styles every
# typed node in the app is drawn in (ui.widgets._NODE_GLYPHS), so this row *is* the
# marker language rather than a restatement of it. The star and the unknown ring have
# no type of their own: they take the ``you`` white and ``muted`` grey they carry
# everywhere else.
nodes = Text("nodes ")
nodes.append("★ ", style="you")
for mark, style in (
("● ", "type.node"),
("▲ ", "type.repeater"),
("■ ", "type.room"),
("◉ ", "type.sensor"),
("○", "muted"),
):
nodes.append(mark, style=style)
lines.append(nodes)
classes = Text("classes ")
for icon in KIND_ICONS.values():
classes.append(glyph(icon) + " ", style="brand")
classes.append(" payloads ", style="muted")
for typename in ("REQ", "RESPONSE", "TEXT_MSG", "GRP_TXT", "GRP_DATA", "ANON_REQ"):
classes.append(glyph(PAYLOAD_ICONS[typename]) + " ")
for typename in ("PATH", "TRACE", "MULTIPART", "CONTROL"):
classes.append(glyph(PAYLOAD_ICONS[typename]) + " ")
lines.append(classes)
channels = Text("channels ")
channels.append(channel_glyph("#public", None) + " name-derived ", style="brand")
channels.append(glyph("🌐") + " public ")
channels.append(glyph("🔒") + " private")
lines.append(channels)
concepts = Text("concepts ")
for icon in ("📡", "🕒", "🔄", "💾", "📂", "🔑", "🗑", "✎", "⚡", "⭐", "📤", "📨"):
concepts.append(glyph(icon) + " ")
for icon in ("🔔", "🔕", "📱", "🔗", "🏆", "⌨", "📖", "💰"):
concepts.append(glyph(icon) + " ")
lines.append(concepts)
lines.append(Text("keys ⌫ ⇧ … ↕ ↔ ↻ ⌖ ❯ ▸"))
lines.append(Text())
names = Text("names ")
for name, key in _DEMO_NAMES:
names.append(name + " ", style=name_style(name, key))
names.append("me", style="you")
lines.append(names)
# The same names as chips — the chat's sender labels, drawn through the path line's
# own segment, so this row is also a one-hop sample of the route language: hue in the
# fill, our end the star, a node nobody can place in the keyless grey.
chips = Text("chips ")
for name, key in _DEMO_NAMES[:2]:
chips.append_text(name_chip(name, key))
chips.append(" ")
chips.append_text(name_chip("", you=True))
chips.append(" ")
chips.append_text(name_chip("·"))
lines.append(chips)
lines.append(Text("heard ").append_text(_ages_row()))
snr = Text("snr ")
for reading in (8.0, -2.0, -12.0):
snr.append(f"{reading:+.1f} ", style=snr_style(reading))
lines.append(snr)
# What the render boundary makes of stored text: accents fold on PicoCalc (the raw
# form would be tofu there — the console font has no accented Latin), Cyrillic is
# font-native and passes through on both platforms.
folded = fold_text("café Montréal")
lines.append(Text.assemble("text ", (folded, "brand"), (" · ", "muted"), "привет мир"))
lines.append(Text())
chart = Text("chart ")
chart.append("⡀⡄⡆⣆⣦⣶⣷⣿", style="snr.good")
chart.append("⣶⣤⣀", style="snr.ok")
chart.append("⡀⠄⠂", style="snr.bad")
chart.append(" braille", style="muted")
lines.append(chart)
# The F-key lane, on the one platform that draws it: both fills, plus a dim slot and
# an unassigned one, so the card shows every state a chip can be in. Built through
# lane_text itself — this is the acceptance screen, so it must be the real renderer.
if platform.footer_fkeys:
lane = (
FPair("Region", "home"),
FPair("You", "locate", enabled=False),
None,
FPair("Page ↓", "pagedown", "Bottom", "end"),
FPair("Page ↑", "pageup", "Top", "home"),
)
lines.append(Text())
lines.append(lane_text(lane))
lines.append(lane_text(lane, shifted=True))
return lines