A new make_name_key_resolver (beside its trace_runner siblings) maps a display name back to the node's key — contacts win, the recorder's stored advert names fill in strangers — and name_style's keyless fallback becomes muted: colour now always marks a keyed identity, never a character-sum accident that two spellings of one node disagree on. Wired through every keyless call site: the chat transcript's sender headers and @mentions (a direct thread falls back to the peer's key), the picker previews, the live feed's channel-sender lane, and the route graph's origin label (new key_of hook on route_graph_style, threaded through the packet viewer and Message paths). The chat picker's contact dot switches to the standard companion pink — shape still marks history — and contact labels take their key-derived hue. name_rgb guards the muted style so braille canvases render the grey. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7.2 KiB
MeshTerm — working notes for Claude
MeshTerm is a full-screen terminal companion for MeshCore LoRa mesh devices. The UI is a
prompt_toolkit session rendering Rich content (meshterm/ui/), tools register into a
menu (meshterm/tools/), services run in the background (meshterm/services/), and
everything heard is recorded to SQLite (meshterm/persistence/).
Run the tests with python -m pytest -q. Screens must stay readable at 72 columns.
Git workflow
Commit directly on main — do not create topic branches unless explicitly asked (this
is a solo repo; branches just create divergence to merge back later). Commit freely, but
never git push unless explicitly asked in that moment.
UX standards
These are binding. Every new screen, dialog, row, or hint follows them; when you touch an old one that doesn't, bring it along. The enforcement points live in code — build through them instead of hand-rolling:
ui/menus.py—back_rows,exit_rows,menu_rows,lane_row,section_heading,confirm_discard,fit_cells.ui/widgets.py—highlighted_hash(THE key widget — shows a key, lights its hash),format_ago(prose ages),_format_age(column ages),channel_glyph,_NODE_GLYPHS, heard-age heat colouring.ui/theme.py—name_style/node_style(per-node hues, hash-derived),snr_style, theyouwhite.
Lexicon — one term per concept
| Term | Meaning |
|---|---|
| node | any mesh participant |
| contact | a node the device knows (has a key) |
| heard | received from ("last heard", "first heard") — never "seen" in UX text |
| key | the full fixed-length value — a node's public key, a channel secret |
| hash | the short derived id — a key's first path-hash-mode bytes (the slice highlighted_hash lights), a channel hash, a path hop |
| path | an ordered hop spec you compose or force (a1,3d,…) |
| route | the concrete node sequence a trace walked or will walk |
| via | prefix for a packet/message's relay chain |
| Back | leave the current screen/list (the only exit word on rows) |
| Quit | leave the app (main menu, device splash) — nowhere else |
Relative ages: format_ago for prose ("now", "5m ago", "never" — never "now ago"),
_format_age for aligned columns ("now", "5m").
Screens and lists
- Every select list ends with the exit group from
back_rows()/exit_rows(): exactly one blankSeparator(" ")line, then the bare word Back (no arrow, no icon). Editors with staged changes show✓ Apply n staged changesover✗ Back — discard staged changes. - Grouped-list section headings use
section_heading("Label")→── Label ──accent. - Label + description command rows go through
menu_rows(two cell-aligned lanes, description muted). Editor rows (setting/value/description) go throughlane_row. - A row that opens further prompts ends with
…; a row that acts immediately doesn't. - Empty states are lowercase muted, optionally
— explanation, never parenthesized. - Body section headings inside screens: accent title, optional muted
· note.
Titles
- Sentence case, always ("Trace — YUL-Poly", "Nodes", "Path width", "Admin login").
- No emoji in any screen or dialog title — icons live in menu/list rows. (A terminal that draws an emoji narrower than Rich measures leaves a content-sized dialog's border short.)
—(em dash) introduces the subject/qualifier:Feature — subject.·chains status atoms:Map · z12 · 34 nodes · 2.1 km across.
Footer hints
- ≤72 cells. Sentence shape: navigation keys, then action keys, Esc last.
- Esc verb by surface:
Esc backleaves a screen ·Esc closedismisses a read-only floating view ·Esc cancelabandons a prompt/dialog ·Esc keepleaves a value picker unchanged ·Esc quitonly at the main menu. - Key notation:
↑↓ move(include the verb),^R/^Endfor Ctrl chords,⌫for backspace,⇧for shift.
Dialogs
- Modal popups over the pushed backdrop (never full-screen replacements).
- Buttons are reverse-video chips (
Label,selectedstyle): safe way out on the left, the committing verb on the right and default, so Enter commits and Esc backs out. Two escalating caution tiers theme the frame:danger=True(amber) for a disruptive choice — discard edits, reboot, show private key — anddestructive=True(the reserved red) for irreversible data loss, so a delete confirm reads red like itstyped_confirmsibling. Bulk or irreversible deletions gate behindtyped_confirm(also red); a single-record delete is a red Cancel/Delete confirm. - Confirms are Cancel/Verb button dialogs, not Yes/No. Prompt lines that ask for input end with a colon.
- Dialog
titleis short; the question/instruction goes inprompt.
Marks and icons — one glyph per concept
- Status marks (single-width, themed):
✓ok ·✗err ·⚠/?warn ·●unread/ unacked (err) ·○acked/empty (muted). Never✔,✖,✅,❌as status. - Node types (shared with the map):
★you (yellow) ·●node ·▲repeater ·■room ·◉sensor ·○unknown. - Concept icons: 📡 advert · 🕒 clock/sync · 🔄 reboot · 💾 backup · 📂 restore ·
🔑 channel/credential key · 🔐 identity/auth secret · 🗑 clear/delete · ✎ compose/edit ·
⚙ parameter ·
#count · ▶ run · ⚡ explore/probe · ★ best/winner · ⭐ watch · 📤 send now · 📨 courier/queue · 💬 chat · 🔔 notify · 🔕 mute (notifications off) · 📱 QR · 🔗 link · ↻ re-read · ↕ reorder · ⇄ reverse (flip a path's direction) · 🏆 trophy case/record · ⌨ command line · 🚪 quit. Packet-class icons (feed/viewer lane): 📢 advert · 📊 telemetry · 📦 packet · 💬 message · ✅ ack. Raw payload classes (PAYLOAD_ICONS): 📻 channel text · 💽 channel data · 📩 direct message (overheard) · 📥 request · 📮 response · 🎭 anon request · 🧭 path · 🎯 trace · 🧩 multipart · 🧰 control · ❔ unknown — raw advert/ack reuse 📢/✅.
Colour
- Node names are always coloured:
name_style(name, key)palette hue, derived from the node's key (its first byte — any known prefix agrees) so a rename keeps the colour. A surface holding only a name resolves it first (make_name_key_resolver); a name no known node carries stays muted — colour is reserved for keyed identities, never seeded from a name's characters. Our own node is the pure-whiteyoustyle; a context colouring (chart quality) may still win. Recency heat colours heard/first-heard ages, never names. A key lane (viahighlighted_hash) lights its hash in the same key-derived hue; the rest of the key, and any key standing in as a name, stays muted. UX text says "key" for the lane and "hash" only for the short derived id — never "hash" for a truncated key. - SNR always through
snr_style; timelines oldest→now left-to-right, grey baseline = 0, drawn viaui/braillechartonly.
Layout
- Wrapped labelled rows hang under their value block (two-column grid), never column 0.
- Dialogs anchor slightly above true centre, sized for their populated state.
- Radio traffic: single transmissions or a user-chosen sample count with cooldown pacing — never bursts.