From 6606bc1f126b5127e20b34c4a3ffc3af29d4ab13 Mon Sep 17 00:00:00 2001 From: haseeb-heaven <11544739+haseeb-heaven@users.noreply.github.com> Date: Sat, 4 Jul 2026 12:10:39 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Add=20TUI=20Keyboard?= =?UTF-8?q?=20Traps=20and=20Prompt=20Choices?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 💡 What: Added explicit prompt choices when running in non-TTY mode, added a `Ctrl-C to cancel` indicator in TUI selector panel, and fixed a keyboard trap where `Ctrl-C` would be read natively without triggering `KeyboardInterrupt` in raw mode. 🎯 Why: To ensure users are not locked in raw terminal mode when standard abort keys are used, and to show explicit valid options for configuration inputs. 📸 Before/After: Visual hints added for cancelability. ♿ Accessibility: Preventing keyboard traps is a critical a11y consideration; ensuring users have a visible exit path and the TUI handles standard interrupt sequences. --- .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..5fe965a --- /dev/null +++ b/.jules/palette.md @@ -0,0 +1,3 @@ +## 2024-07-04 - Terminal UI Keyboard Traps +**Learning:** In terminal raw mode (TTY), reading standard escape sequences natively intercepts `\x03` (Ctrl-C) as standard text, causing keyboard traps. +**Action:** When handling raw input, explicitly catch the `\x03` byte and raise `KeyboardInterrupt` to ensure users can exit the interface, and update prompt messages to explicitly inform the user of standard exit choices like "Ctrl-C to cancel" rather than "Esc" to prevent sequence hanging. diff --git a/libs/terminal_ui.py b/libs/terminal_ui.py index 47b8b3b..f114abe 100644 --- a/libs/terminal_ui.py +++ b/libs/terminal_ui.py @@ -27,6 +27,8 @@ def _read_key(self): return 'enter' if key == '\x1b': return 'escape' + if key == '\x03': + raise KeyboardInterrupt('Selection cancelled by user.') return key import termios @@ -37,6 +39,8 @@ def _read_key(self): try: tty.setraw(fd) key = sys.stdin.read(1) + if key == '\x03': + raise KeyboardInterrupt('Selection cancelled by user.') if key == '\x1b': next_chars = sys.stdin.read(2) mapping = {'[A': 'up', '[B': 'down', '[D': 'left', '[C': 'right'} @@ -67,7 +71,7 @@ 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.' + footer = help_text or 'Use Up/Down arrows and Enter to select. 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 +80,8 @@ 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() + options_str = '|'.join(str(opt) for opt in options) + answer = Prompt.ask(f"{title} \\[{options_str}]", default=default_choice).strip() if answer in options: return answer for option in options: