Three kinds of "setting" were tangled into two files and one of them had no screen at all. The radio's settings live on the radio and are edited on Device config. Machine setup -- where the database is, which companion to talk to -- stays in config.toml, a text editor's job. Everything in between was app behaviour: six keys sitting in config.toml with nothing to open them, and a dozen more that were only module constants a reader could never reach. Those are preferences now, and they have a page. core/preferences.py is a PrefSpec registry in the shape of the device-config one -- group, type, bounds, help, and a default -- and the page is built from the same parts as the device editor, deliberately: one grouped lane-aligned list, current -> new staged in warn, Apply/Back appearing only once something is staged, the shared discard confirm on the way out. Two editors that stage values should not be two screens to learn. What it adds is a Defaults section holding one row that stages every changed preference back to its built-in value; it is drawn only while a reset would do something, and it stages rather than writes, so it is as reviewable and as Esc-discardable as any other change and the file is still only ever written by the one action at the bottom. Its rows scroll sideways where the device editor's don't: the explanation is the widest thing on the row and the only part that runs past the edge, so <-> slide it under a pinned setting and value. The file is preferences.yaml and holds only the values you disagree with the code about -- grouped and commented, each entry above its help and its default. That is what lets a later change to a default reach an install that never asked to be exempt; delete a line and the code takes over again. A hand edit gone wrong costs that line, never the session. Every preference is wired to something that reads it. ctx.preferences is the explicit path; preferences.current() is for the render layer, which has no context to reach through, the same reason get_platform() exists. The About pages come along into a This app section, the third scope after this node and other nodes, with Preferences leading it -- the one row there that changes MeshTerm rather than describing it. A section of one would have broken the menu's own no-bucket-of-one rule, and the app's scope is the honest home for both halves anyway. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
49 lines
1.8 KiB
Python
49 lines
1.8 KiB
Python
"""Tests for machine-setup loading (where things live, and which device to talk to).
|
|
|
|
MeshTerm's *behaviour* settings are preferences and live in ``tests/test_preferences.py``;
|
|
this file is only what ``config.toml`` still answers for.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from meshterm.core.config import Settings
|
|
|
|
|
|
def test_connect_on_start_defaults_true() -> None:
|
|
"""Eager connect-at-launch is the default when nothing is configured."""
|
|
assert Settings().connect_on_start is True
|
|
|
|
|
|
def test_connect_on_start_loads_from_toml(tmp_path: Path) -> None:
|
|
"""Whether the session opens the radio link at launch round-trips from the config file."""
|
|
config = tmp_path / "config.toml"
|
|
config.write_text("connect_on_start = false\n", encoding="utf-8")
|
|
assert Settings.load(config).connect_on_start is False
|
|
|
|
|
|
def test_tcp_profile_inferred_and_loaded(tmp_path: Path) -> None:
|
|
"""A profile with a ``host`` loads as a TCP profile and exposes its host:port endpoint."""
|
|
config = tmp_path / "config.toml"
|
|
config.write_text(
|
|
"[profiles.wifi]\n"
|
|
'host = "192.168.1.50"\n'
|
|
"tcp_port = 6000\n",
|
|
encoding="utf-8",
|
|
)
|
|
profile = Settings.load(config).profiles["wifi"]
|
|
assert profile.is_tcp and profile.transport == "tcp"
|
|
assert profile.host == "192.168.1.50" and profile.tcp_port == 6000
|
|
assert profile.tcp_endpoint == "192.168.1.50:6000"
|
|
|
|
|
|
def test_tcp_profile_defaults_port(tmp_path: Path) -> None:
|
|
"""A TCP profile naming only a host takes the default port in its endpoint."""
|
|
config = tmp_path / "config.toml"
|
|
config.write_text(
|
|
'[profiles.wifi]\nhost = "meshcore.local"\n', encoding="utf-8"
|
|
)
|
|
profile = Settings.load(config).profiles["wifi"]
|
|
assert profile.is_tcp and profile.tcp_endpoint == "meshcore.local:5000"
|