From 7407358edecd504fcfc97d7a48ba28792daf3762 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Martineau Date: Mon, 31 Aug 2026 03:02:29 -0400 Subject: [PATCH] A button row's Enter belongs to whichever chip is lit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The quit confirm promised "Enter quit · Esc cancel", which held only while Quit was the highlighted button — and on a bonded device there is an "Unpair & quit" between Cancel and Quit, so Enter has three things it might do. The hint also left out ←→, the one key that changes which. Both dialogs (the TX winner's "Enter apply" is the same shape) drop back to the dialog default, which says the mechanism instead of guessing the outcome: ←→ choose · Enter select · Esc cancel. A row-picking list keeps its licence to name a committing verb, because every row commits the same way. A lone-button acknowledgement keeps "Enter OK" too, having nothing to choose between — that is the exception the new standards test walks the package for. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 6 +++- meshterm/ui/menu.py | 4 ++- meshterm/ui/tx_screen.py | 3 +- tests/test_tui.py | 60 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 9c6342d..8f7fd12 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -175,7 +175,11 @@ as a grouped list does. Filling a page in is editing its `.md`; no Python follow `Enter select` when it picks a value or action row · `Enter set` in a value picker. A more specific committing verb (`Enter adopt path`, `Enter add`) is fine; a synonym of the generic three (`pick`, `choose`, `commit`) is not. The filter atom is always - `type to filter`. + `type to filter`. A **button row** is the one place the specific verb is barred: Enter + commits whichever chip is highlighted, so the hint stays `←→ choose · Enter select · + Esc cancel` — `Enter quit` on the quit confirm was true only until ←→ moved, and hid + the key that moves it. A lone-button acknowledgement names its verb (`Enter OK`) and + offers no `←→`, having nothing to choose between. - A hint is drawn **once per frame**, and which surface draws it is the footer's call. Where the footer is the hint line (regular) it carries the *top* screen's hint, so a floating dialog's border stays silent — the same sentence in the border and at the diff --git a/meshterm/ui/menu.py b/meshterm/ui/menu.py index 5afd6ff..4d7d80b 100644 --- a/meshterm/ui/menu.py +++ b/meshterm/ui/menu.py @@ -591,7 +591,9 @@ async def _menu_round( buttons, title="Quit MeshTerm", default=len(buttons) - 1, # highlight Quit - footer_hint="Enter quit · Esc cancel", + # The hint stays the dialog default: Enter commits *the highlighted + # button*, which is Quit only until ←→ moves it — and with a third + # button in the row, naming the key that moves it is the point. prompt_style="warn", button_style="selected", button_idle_style="muted", diff --git a/meshterm/ui/tx_screen.py b/meshterm/ui/tx_screen.py index 3ef8233..77d2547 100644 --- a/meshterm/ui/tx_screen.py +++ b/meshterm/ui/tx_screen.py @@ -854,7 +854,8 @@ async def open_tx_optimize( [("Cancel", "cancel"), ("Apply", "apply")], title="Apply winner", default=1, - footer_hint="Enter apply · Esc cancel", + # The dialog default hint: Enter commits whichever button is highlighted, + # and ←→ is what moves it (see the quit confirm in ui/menu.py). ) if choice != "apply": return diff --git a/tests/test_tui.py b/tests/test_tui.py index b533ff3..f7e5ff3 100644 --- a/tests/test_tui.py +++ b/tests/test_tui.py @@ -2818,3 +2818,63 @@ def test_an_off_screen_rows_live_title_is_left_alone() -> None: lines = screen.render_body(53) lines[0:10] assert calls == ["top"] + + +def _button_dialog_hints() -> list[tuple[str, int, int | None, str]]: + """Every literal ``footer_hint=`` on a button dialog: (file, line, buttons, hint). + + ``buttons`` is the count when the call passes a list literal, else ``None`` — a + computed row (the quit confirm grows a third button on a bonded device) can hold any + number, so it is held to the many-button rule. + """ + import ast + from pathlib import Path + + found: list[tuple[str, int, int | None, str]] = [] + root = Path(__file__).resolve().parent.parent / "meshterm" + for path in sorted(root.rglob("*.py")): + tree = ast.parse(path.read_text(encoding="utf-8")) + for node in ast.walk(tree): + if not isinstance(node, ast.Call): + continue + func = node.func + name = func.attr if isinstance(func, ast.Attribute) else getattr(func, "id", "") + if name not in ("button_dialog", "ButtonDialog"): + continue + hint = next( + (k.value.value for k in node.keywords + if k.arg == "footer_hint" and isinstance(k.value, ast.Constant)), + None, + ) + if hint is None: + continue # the default hint, which is the generic one + row = node.args[1] if len(node.args) > 1 else None + count = len(row.elts) if isinstance(row, ast.List) else None + found.append((path.name, node.lineno, count, hint)) + return found + + +def test_a_button_dialogs_hint_never_names_the_verb_of_one_button() -> None: + """Enter commits *the highlighted* button, so a row of them may only say ``select``. + + The quit confirm used to promise ``Enter quit · Esc cancel``, which was true only + while Quit happened to be highlighted — ←→ moves it, and on a bonded device a third + button sits between the two (JP, 2026-08-31). The same hint also left out the one key + that changes what Enter will do. A row-picking list may name its committing verb + (``Enter adopt path``) because every row commits the same way; a button row cannot. + + A dialog with a single button is the exception the rule is shaped around: a lone + acknowledgement has nothing to choose between, so it names its verb (``Enter OK``) + and does not advertise an ←→ that would move nothing. + """ + for where, line, buttons, hint in _button_dialog_hints(): + if buttons == 1: + assert "←→" not in hint, f"{where}:{line} offers ←→ over a single button" + continue + assert "Enter select" in hint, ( + f"{where}:{line} promises {hint!r} — Enter commits whichever button is " + "highlighted, so the atom stays 'Enter select'" + ) + assert "←→" in hint, ( + f"{where}:{line} hides ←→, the key that moves the highlight Enter commits" + )