Files
MeshTerm/meshterm/ui/specimen.py
T
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

176 lines
7.1 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""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