From 57c5c78e1c56648d97b3ce7be4a7af1c0a6245b9 Mon Sep 17 00:00:00 2001 From: haseeb-heaven <11544739+haseeb-heaven@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:02:19 +0000 Subject: [PATCH] feat: Improve terminal UX accessibility with explicit cancel hints Added explicit keyboard cancel hints (`Ctrl-C to cancel`), gracefully handled `\x03` keystrokes to prevent keyboard traps, and surfaced available choices explicitly in headless inputs. --- .jules/palette.md | 3 +++ libs/terminal_ui.py | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 .jules/palette.md diff --git a/.jules/palette.md b/.jules/palette.md new file mode 100644 index 0000000..7f070a8 --- /dev/null +++ b/.jules/palette.md @@ -0,0 +1,3 @@ +## 2025-01-01 - TUI Shortcuts and Visual Prompts +**Learning:** In headless terminal environments, web-style visual affordances must be explicitly surfaced (e.g., manually formatting options in `rich` prompts without triggering markup parsing). Furthermore, 'Esc' (`\x1b`) should never be advertised as a TUI cancel action in raw mode, as blocking ANSI escape sequence reads can hang the app. +**Action:** Always manually escape choice formatting in `rich` prompts (e.g., `\[a|b]`), use `\x03` (`Ctrl-C`) for TUI cancellation, and append shortcut hints to footer text. diff --git a/libs/terminal_ui.py b/libs/terminal_ui.py index 47b8b3b..4537c4f 100644 --- a/libs/terminal_ui.py +++ b/libs/terminal_ui.py @@ -19,6 +19,8 @@ def _read_key(self): if os.name == 'nt': import msvcrt key = msvcrt.getwch() + if key == '\x03': + raise KeyboardInterrupt if key in ('\x00', '\xe0'): extended = msvcrt.getwch() mapping = {'H': 'up', 'P': 'down', 'K': 'left', 'M': 'right'} @@ -37,6 +39,8 @@ def _read_key(self): try: tty.setraw(fd) key = sys.stdin.read(1) + if key == '\x03': + raise KeyboardInterrupt if key == '\x1b': next_chars = sys.stdin.read(2) mapping = {'[A': 'up', '[B': 'down', '[D': 'left', '[C': 'right'} @@ -67,7 +71,8 @@ def _render_selector(self, title, options, selected_index, help_text, default): style = 'bold green' if index == selected_index else '' table.add_row(marker, label, style=style) - footer = help_text or 'Use Up/Down arrows and Enter to select.' + base_footer = help_text or 'Use Up/Down arrows and Enter to select.' + footer = f"{base_footer} (Ctrl-C to cancel)" self.console.print(Panel.fit(footer, title='Interpreter TUI', border_style='green')) self.console.print(f"[bold cyan]{title}[/bold cyan]") self.console.print(table) @@ -76,7 +81,7 @@ def _render_selector(self, title, options, selected_index, help_text, default): def _select_option(self, title, options, default, help_text=None): if not sys.stdin.isatty(): default_choice = default if default in options else options[0] - answer = Prompt.ask(f"{title}", default=default_choice).strip() + answer = Prompt.ask(f"{title} \\[{'|'.join(options)}]", default=default_choice).strip() if answer in options: return answer for option in options: