A button row's Enter belongs to whichever chip is lit
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) <noreply@anthropic.com>
This commit is contained in:
1 parent
2a7d01108b
commit
7407358ede
4 files changed
+70
-3
No files matched your search
@@ -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
|
||||
|
||||
+3
-1
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
Reference in new issue
Block a user