Footer hints: one grammar, Esc always last, one key notation

The key hints now follow one sentence shape across every screen:
navigation keys first, action keys next, Esc last — the quit dialog and
TX apply dialog had Esc leading. The Esc verb is now systematic: "Esc
back" leaves a screen, "Esc close" dismisses a read-only floating view
(result windows and the share/key popups switch over), "Esc cancel"
abandons a prompt, "Esc keep" leaves a value picker unchanged. Bare
hints fill in their missing verbs ("↑↓ move", the map's and composer's
naked "Esc"), the confirm prompt says "Enter select" like its button
siblings, and key notation unifies on the compact forms the app already
half-used: ^R/^End for Ctrl chords (chat's "Ctrl-R" included) and ⌫ for
backspace (the map/atlas "Bksp").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jean-Pierre MartineauandClaude Opus 4.8 committed 2026-07-12 03:59:22 -04:00
1 parent d1c8ed73a0
commit 84a2c45f12
14 files changed
+23 -22

No files matched your search

+1 -1
View File
@@ -184,7 +184,7 @@ class AtlasScreen(Screen):
def footer_hint(self) -> str: # type: ignore[override]
"""Camera and find hints; the find query takes the row over while active."""
if self._filter:
return f"find: {self._filter}▏ · Enter select · Bksp erase · Esc clear"
return f"find: {self._filter}▏ · Enter select · ⌫ erase · Esc clear"
return "↑↓←→ pan · PgUp/PgDn zoom · Tab walk · Enter info · type to find · Esc"
def handle(self, action: str, data: str = "") -> None:
+2 -2
View File
@@ -739,7 +739,7 @@ async def _show_share(
Text(""),
Text(url, style="accent"),
)
await ctx.ui.view(body, title=f"Share {name}", footer_hint="Esc back")
await ctx.ui.view(body, title=f"Share {name}", footer_hint="Esc close")
async def _show_key(ctx: "AppContext", slot: ChannelSlot) -> None:
@@ -775,7 +775,7 @@ async def _show_key(ctx: "AppContext", slot: ChannelSlot) -> None:
blocks.append(Text())
blocks.append(Text(label, style="muted"))
blocks.append(value)
await ctx.ui.view(Group(*blocks), title=f"Key — {slot.name}", footer_hint="Esc back")
await ctx.ui.view(Group(*blocks), title=f"Key — {slot.name}", footer_hint="Esc close")
async def _open_chat(ctx: "AppContext", slot: ChannelSlot) -> None:
+2 -2
View File
@@ -167,7 +167,7 @@ class ChatScreen(Screen):
"""Key hint, reflecting whether a message is picked for reply (channels only)."""
if not self._is_channel:
if any(m.outbound and m.acked is False for m in self._messages):
return "Enter send · Ctrl-R retry failed · ↑↓/PgUp scroll · ^End latest · Esc back"
return "Enter send · ^R retry failed · ↑↓/PgUp scroll · ^End latest · Esc back"
return "Enter send · ↑↓/PgUp scroll · ^End latest · Esc back"
if self._selected is not None:
return "Enter reply (@mention) · ↑↓ pick · ^End/Esc cancel"
@@ -736,7 +736,7 @@ class ChatScreen(Screen):
message.acked = False
self._status = f"retry failed: {exc}"
else:
self._status = "" if message.acked else "still no ack — Ctrl-R to retry"
self._status = "" if message.acked else "still no ack — ^R to retry"
finally:
self._sending = False
self._stick = True
+1 -1
View File
@@ -816,7 +816,7 @@ async def _show_contact_card(ctx: "AppContext", snapshot: dict) -> None:
Text(""),
Text(url, style="accent"),
)
await ctx.ui.view(body, title=f"Share {name}", footer_hint="Esc back")
await ctx.ui.view(body, title=f"Share {name}", footer_hint="Esc close")
async def _reboot(ctx: "AppContext", device: "Device", snapshot: dict) -> bool:
+1 -1
View File
@@ -290,7 +290,7 @@ async def _pick_schedule(ctx: "AppContext", name: str):
]
picked = await session.select(
f"When should {name} get it?", items, filterable=False,
footer_hint="↑↓ move · Enter choose · Esc cancel",
footer_hint="↑↓ move · Enter select · Esc cancel",
)
if picked == "custom":
while True:
+2 -2
View File
@@ -125,9 +125,9 @@ class MapScreen(Screen):
editing keys, so the typed text is always visible somewhere fixed.
"""
if self._filter:
base = f"find: {self._filter}▏ · Enter frame · Bksp erase · Esc clear"
base = f"find: {self._filter}▏ · Enter frame · ⌫ erase · Esc clear"
else:
base = "↑↓←→ pan · ⇧ fine · PgUp/PgDn zoom · Home reset · type to find · Esc"
base = "↑↓←→ pan · ⇧ fine · PgUp/PgDn zoom · Home reset · type to find · Esc back"
if self._pending:
return f"{base} · [muted]loading {len(self._pending)} tiles…[/muted]"
if not self._source.available:
+1 -1
View File
@@ -422,7 +422,7 @@ async def _menu_loop(ctx: AppContext, session: TuiSession) -> None:
buttons,
title="Quit MeshTerm",
default=len(buttons) - 1, # highlight Quit
footer_hint="Esc cancel · Enter quit",
footer_hint="Enter quit · Esc cancel",
prompt_style="warn",
button_style="selected",
button_idle_style="muted",
+1 -1
View File
@@ -98,7 +98,7 @@ class PathComposerScreen(Screen):
:data:`~meshterm.ui.tui.screen.CANCEL`.
"""
footer_hint = "↑↓ · Enter add · type to filter · ⌫ remove · Esc"
footer_hint = "↑↓ move · Enter add · type to filter · ⌫ remove · Esc cancel"
def __init__(
self,
+2 -2
View File
@@ -1255,7 +1255,7 @@ async def _open_session(ctx: "AppContext", target: Optional[str]) -> int:
items,
prompt="Forced hops are addressed by this many leading key bytes.",
default=width_bytes,
footer_hint="↑↓ · Enter set · Esc keep",
footer_hint="↑↓ move · Enter set · Esc keep",
filterable=False,
wrap=False,
)
@@ -1292,7 +1292,7 @@ async def _open_session(ctx: "AppContext", target: Optional[str]) -> int:
items,
prompt=f"One Trace action runs this many traces, {pace:g} s apart.",
default=sample_count,
footer_hint="↑↓ · Enter set · Esc keep",
footer_hint="↑↓ move · Enter set · Esc keep",
filterable=False,
wrap=False,
)
+1 -1
View File
@@ -332,7 +332,7 @@ class ConfirmScreen(Screen):
title: str,
*,
default: bool = True,
footer_hint: str = "←→/Y/N choose · Enter accept · Esc cancel",
footer_hint: str = "←→/Y/N choose · Enter select · Esc cancel",
) -> None:
"""Build a confirm prompt.
+1 -1
View File
@@ -268,7 +268,7 @@ class ScrollScreen(Screen):
renderable: RenderableType,
*,
title: str = "",
footer_hint: str = "↑↓ PgUp/PgDn scroll · Esc back",
footer_hint: str = "↑↓ PgUp/PgDn scroll · Esc close",
floating: bool = True,
) -> None:
"""Wrap a renderable in a dismissable, scrollable screen.
+1 -1
View File
@@ -550,7 +550,7 @@ class TuiSession:
screen = ScrollScreen(
renderable,
title=title,
footer_hint=footer_hint or "↑↓ PgUp/PgDn scroll · Esc back",
footer_hint=footer_hint or "↑↓ PgUp/PgDn scroll · Esc close",
)
await self.run_screen(screen)
+4 -4
View File
@@ -675,7 +675,7 @@ async def open_tx_optimize(
items,
prompt="The TX window the sweep explores:",
default=(screen.tx_min, screen.tx_max),
footer_hint="↑↓ · Enter set · Esc keep",
footer_hint="↑↓ move · Enter set · Esc keep",
filterable=False,
wrap=False,
)
@@ -714,7 +714,7 @@ async def open_tx_optimize(
items,
prompt="The coarse sweep measures the window at this spacing, then refines.",
default=screen.step,
footer_hint="↑↓ · Enter set · Esc keep",
footer_hint="↑↓ move · Enter set · Esc keep",
filterable=False,
wrap=False,
)
@@ -739,7 +739,7 @@ async def open_tx_optimize(
items,
prompt=f"Each TX level is measured with this many traces, {pace:g} s apart.",
default=screen.samples,
footer_hint="↑↓ · Enter set · Esc keep",
footer_hint="↑↓ move · Enter set · Esc keep",
filterable=False,
wrap=False,
)
@@ -804,7 +804,7 @@ async def open_tx_optimize(
[("Cancel", "cancel"), ("Apply", "apply")],
title="Apply winner",
default=1,
footer_hint="Esc cancel · Enter apply",
footer_hint="Enter apply · Esc cancel",
)
if choice != "apply":
return
+3 -2
View File
@@ -668,8 +668,9 @@ def test_chat_screen_shows_delivery_glyphs() -> None:
# static hourglass; resolved ones show ✅ / ❌.
assert "✅" in joined and "❌" in joined
assert Spinner.BRAILLE[0] in joined and "⏳" not in joined
# A failed message advertises the retry shortcut in the footer hint.
assert "Ctrl-R" in screen.footer_hint
# A failed message advertises the retry shortcut in the footer hint (the app-wide
# compact ^-notation for Ctrl chords).
assert "^R" in screen.footer_hint
def test_wrapped_body_hangs_under_the_first_line() -> None: