Every repaint re-rasterized every row and re-ran every layout; every
screen open re-scanned the whole history. Now work is done once per
change instead of once per frame:
Render pipeline — a content-keyed ANSI cache in render_to_ansi (every
Text/str row app-wide), memoized corner glow, and panel-wrap memos for
the base frame and stacked dialogs, all dropped on a platform switch or
emoji recalibration. Idle-frame compose falls 5-100x per screen
(contacts 5.6->0.1 ms, record card 8.5->0.07 ms, trace 7.6->0.6 ms).
Repository — an observed_at index turns every since-window query into a
range walk (recent_observations 7.5->1 ms); heard_nodes and first_seen
aggregate in one streaming pass by ISO-string comparison instead of
hydrating dataclasses and sorting; node_names groups in SQL (NOT
INDEXED past a slow planner choice).
Per packet — livefeed no longer repaints on events that drew nothing,
the channel list's stats TTL rises above the tick so the repository is
not queried per frame, watch-store alerts batch through the throttled
flush instead of a full-state disk write per new node, and the header's
360-bucket histogram/flags tuples are memoized per minute and packet.
Per open — the resolvers, topology canonical()/display_name(), and
Yen's route search memoize over their frozen inputs; timemachine, chat,
and livefeed drop duplicate queries; the config editor, device actions,
info, advert, and tx-optimize reuse the devstate session cache instead
of re-asking the radio.
Per frame, per screen — dashboard folds five window scans into one
digest; walk caches everything derived from its frozen graph (the
onward-count quadratic included); packet viewer, record dialog, trophy
rows, trace stats/log, and the node-detail route fan memoize on their
frozen inputs, invalidated by revision counters where the input can
move.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The chat picker and the TX-optimize target picker appended their own
Separator + Back rows; back_rows is the enforcement point that keeps the
exit language from drifting (housekeeping H-004).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The viz package existed solely to render one interactive HTML chart for
tx-optimize sweeps, pulling in Plotly — a heavyweight dependency serving a
single artifact while every other timeline renders in-terminal via
braillechart. Drop it with no replacement: delete meshterm/viz, the
--viz/--no-viz flag and its render call, the now-unused output_dir setting
(config field, default, loader, example), the Plotly test, and the README
and pyproject references.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The admin-login password prompt ran on an empty stack (the picker was popped
when it returned), so session.text pushed a blank base and the prompt — and any
"wrong password" rejection — floated over an erased background.
open_repeater_admin now redraws the node picker as a static backdrop (extracted
admin_picker_rows, shared with the live pick so they can't drift) and keeps it
pushed across _login, with the picked node highlighted. The password box and the
rejection float over the picker, matching the device-picker and trophy-case
dialogs. floating=True stays on the prompt as a belt-and-suspenders fallback.
Also drop the inert floating=True on tx_optimize._resolve_password: that
resolver only runs on the scripted CLI path (PlainUi, no stack), so the flag
never did anything — the menu path logs in inside the pushed sweep screen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enforce the UX standard "modal popups over the pushed backdrop" across three
flows that were drawing a full-frame replacement instead of a floating popup:
- Trophy case: keep the record browser pushed as a static backdrop while its
delete-record / delete-discipline / delete-all confirms float over it, and
run any trace hand-off after popping.
- Device picker: pass the picker's rows through to the removal confirm so it
floats over a redraw of the list (backdrop_items/backdrop_default on
confirm_startup), with the row being removed left highlighted.
- Remote-admin password: add an opt-in floating flag to Ui.text so a mid-flow
password prompt draws as a centered popup over a blank base instead of the
full frame; apply it to repeater_admin and tx_optimize logins. Tool-entry
text prompts (Trace/tx target fallbacks) stay full-frame.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The session cache fixed the reads screens make on open, but opening the
heaviest screens still stalled for seconds — because the pickers and
screen-openers that run *in front of* those screens were still reading the
radio directly. The menu wraps prompt-gathering and execution in one busy
overlay, so a Trace or Chat open pays its picker's slow round-trips before
the (already-cached) screen behind it ever builds, which is why caching the
screens alone showed no improvement.
Route the interactive-open reads through ctx.devstate too:
- Chat's conversation picker read the channel-slot probe *and* the contacts
table on every open (~5s) — both now come from the cache (channels mapped
from the cached slots via the new _channels_from_slots, contacts served
cached). The CLI list/send path keeps reading fresh.
- Trace and TX-optimize target pickers now read contacts from the cache.
- The repeater-admin screen and the channels manager (its slot probe, on a
local copy so mutations still re-probe the fresh layout after invalidation)
read through the cache.
Small, session-stable reads (self-info, build_snapshot's tuning/path-hash)
are left as direct reads — they were never the seconds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A new Device management entry for administering repeaters and room
servers. The shared credentialed-first picker (extracted from TX
optimize) chooses the node, login runs on remembered passwords with a
one-time prompt otherwise (forgotten on rejection), and the editor
lands in the local device-configuration screen's exact lanes — but
speaking the node's text CLI through a new public
Device.send_remote_command, which brings the repeater-only knobs the
local editor never had: TX delay, Direct TX delay, RX delay, airtime
factor, advert intervals, flood max, repeat, guest password.
Reads cost one paced mesh round trip each, so the editor never
bulk-reads on open: values show what the last read said, age-stamped,
from a new per-node remote.json cache; Read settings refreshes them
under an abortable progress dialog and Apply sends the staged sets
the same way, folding confirmed values back into the cache and
keeping refused ones staged. Advert, clock sync, password change, and
reboot run as confirmed popups.
The Command line is a readline-style screen onto the node's raw CLI:
per-node history persisted across sessions (↑↓ recall, adjacent-dupe
suppression, capped), Tab completion over the known verbs plus every
catalog get/set spelling with ghost-text preview, one command in
flight at a time. The simulator grows a full repeater CLI so all of
it drives against --mock, and a scripted one-shot
(repeater-admin <node> <command…>) covers automation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The old flow asked for a hand-typed comma-separated path, logged in
before showing anything, and started transmitting the moment the
screen opened, on parameters you couldn't touch. It now walks like the
trace feature: two pickers choose the link — the node to tune
(credentialed repeaters lead, wearing the key glyph), then the target
whose reception is optimized — and the screen opens armed but idle.
An action list drives it: Route opens the trace path composer pinned
at the tuned node (suggestions from observed links; direct by
default), Range/Step/Samples float small dialogs, and the Sweep row
always quotes the worst-case transmission count, so the cost of a
commit is on screen before anything goes on the air (coarse_levels
goes public to price it). Sweep logs in first — the password prompt
floats only when nothing is remembered — then runs coarse → refine →
verify under the trace tool's in-flight dialog with Abort, levels
landing in the bar chart behind it, best starred. Completion parks the
cursor on a new Apply-winner action row (the after-sweep dialog's
second chance), and a finished sweep leaves the screen armed: adjust
and sweep again, the chart clearing as a new measurement starts.
The scripted CLI keeps its one-shot flag set, table, and HTML chart.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Messaging becomes Mesh and takes in Nodes (between Channels and Map —
the "who's out there" view) and Send advert (last in section: its one
action, after its views), so everything mesh-wide lives under one
heading. Device becomes Device management and keeps the box-level
tools; Diagnostics becomes Tools.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tools gain an icon attribute — menu-only, so result windows and the
CLI keep the plain title — and the menu loop composes it into the
aligned name lane. Single-codepoint, emoji-presentation glyphs only,
so the two-cell width holds across terminals: 💬 Chat, 📻 Channels,
🌍 Map, 👥 Nodes, 📡 Send advert, 🔧 configuration, 🔨 actions,
📋 info, 🎯/👣 the traces, 🎧 Monitor, 📶 TX optimize, 🚪 Quit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The six-prompt chain (path, samples, min, max, step, apply-up-front)
collapses to one: pick the path, and the live sweep screen takes it
from there. Levels land in an SNR bar chart as they are measured,
ascending by power with the current best starred, while the optimizer
announces its phase — coarse sweep, refining around the leader,
verifying — through a new on_phase callback. Reliability and trace
counts sit beside every bar, and a level nothing got through at reads
as an explicit no-reply row.
The apply decision moves to where it belongs: after the sweep, a
floating dialog offers the winner against the node's original power
(Cancel left, Apply right, Enter commits), and `a` re-offers it if
first declined. The sweep itself runs with apply off, so Esc at any
point stops the search and the optimizer's unwind restores the
original transmit power.
The sweep persists under one runs row (levels and traces alike), the
scripted CLI keeps its full flag set, chart, and --apply semantics,
and the tool moves in with Trace under Diagnostics.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five features leave the menu and CLI: the heard-nodes review, the
baseline-regression checker, the offline link-budget calculator, the
route-stability graph, and the run-history browser — along with their
services (link_quality, link_budget, route_stability), the pyvis route
renderer, and the pyvis/networkx dependencies.
The data layer is untouched where it still earns its keep: passive
monitoring keeps recording every overheard packet (the header counters,
Nodes, and Map read it, and `meshterm monitor` still captures a bounded
window from the CLI), run logging keeps writing `runs` rows, and trace
history stays queryable for the trace tools. Only the route-map-only
`all_traces` query goes.
Also fixes a latent tie-break flake in the nodes sort: ages are now
measured from one clock snapshot, so identical last-seen stamps really
tie and keep their A→Z order in both directions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Enforce one voice across every screen: column and section headers are
UPPERCASE, menu items are Capitalized buttons, and short descriptions
drop their trailing period (with real ellipses instead of "...").
Tools gain a display `title` separate from their kebab-case CLI name,
so a menu item now reads exactly like the screen it opens ("Device
configuration" opens Device configuration) and result windows inherit
the same name. Every sub-screen backs out via a "Back" row below a
blank separator (the config editor was missing both; its Close is now
Back, spelling out the discard when changes are staged), while the main
menu and device picker keep "Quit". Panel and dialog titles now wear a
brighter tint of their border colour via new title.* theme styles.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rename the package directory, imports, brand strings, env-var prefixes,
console-script entry point, and the config/data dir (~/.config/meshterm).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>