diff --git a/.jules/palette.md b/.jules/palette.md new file mode 100644 index 00000000..6a8c0bc4 --- /dev/null +++ b/.jules/palette.md @@ -0,0 +1,3 @@ +## 2025-05-20 - TUI Accessibility and Keyboard Traps +**Learning:** Terminal UIs can trap keyboard users when standard interrupt bytes (\x03) are caught by raw mode without an explicit exit path, and headless prompts without inline choices lack intuitiveness. +**Action:** Always map \x03 to explicitly raise KeyboardInterrupt in TUI raw mode, include inline choices for text prompts, and provide explicit shortcut hints (Esc/Ctrl-C) in footers. diff --git a/libs/terminal_ui.py b/libs/terminal_ui.py index 47b8b3bd..70756313 100644 --- a/libs/terminal_ui.py +++ b/libs/terminal_ui.py @@ -43,6 +43,8 @@ def _read_key(self): return mapping.get(next_chars, 'escape') if key in ('\r', '\n'): return 'enter' + if key == '\x03': + raise KeyboardInterrupt('Selection cancelled by user.') return key finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) @@ -67,7 +69,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. (Esc/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 +78,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: @@ -110,7 +112,7 @@ def _select_option(self, title, options, default, help_text=None): def _select_boolean(self, title, default=False): default_choice = 'yes' if default else 'no' - choice = self._select_option(title, ['yes', 'no'], default_choice, 'Use Up/Down arrows and Enter to choose.') + choice = self._select_option(title, ['yes', 'no'], default_choice, 'Use Up/Down arrows and Enter to choose. (Esc/Ctrl-C to cancel)') return choice == 'yes' def select_mode(self, default_mode='code'):