Skip to content

Commit 428310b

Browse files
committed
🎨 Palette: [UX improvement] Fix TUI keyboard traps and improve discoverability
💡 What: Mapped `\x03` (Ctrl-C) to 'escape' in terminal raw input loops for both Windows and Unix paths. Added an explicit `(Esc/Ctrl-C to cancel)` hint to the TUI footer. Modified the non-TTY `Prompt.ask` to explicitly display available choices directly in the prompt string. 🎯 Why: In raw terminal mode, standard OS interrupts are often swallowed, trapping users in the TUI without a clear way out if they try to Ctrl-C. Furthermore, omitting shortcuts in the TUI footer and hiding choices in the non-TTY fallback leaves users guessing what to do next. ♿ Accessibility: Improves terminal keyboard accessibility by ensuring standard exit patterns (Ctrl-C, Esc) reliably function to cancel out of TUI menus, and enhances cognitive accessibility by explicitly visualizing available choices and shortcuts.
1 parent 2a47494 commit 428310b

2 files changed

Lines changed: 10 additions & 3 deletions

File tree

‎.jules/palette.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## 2024-05-29 - TUI Keyboard Traps and Discoverability
2+
**Learning:** In terminal environments, raw input modes can swallow standard OS interrupt signals (like `\x03` for Ctrl-C), trapping users. Additionally, TUI elements without explicit shortcut hints and non-TTY prompts without explicit choices leave users guessing.
3+
**Action:** Always map standard interrupt bytes to exit actions in raw mode, explicitly render cancel shortcuts (e.g., 'Esc/Ctrl-C to cancel'), and format choices directly into non-TTY prompts avoiding strict validation pitfalls.

‎libs/terminal_ui.py‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def _read_key(self):
2525
return mapping.get(extended, extended)
2626
if key == '\r':
2727
return 'enter'
28-
if key == '\x1b':
28+
if key in ('\x1b', '\x03'):
2929
return 'escape'
3030
return key
3131

@@ -43,6 +43,8 @@ def _read_key(self):
4343
return mapping.get(next_chars, 'escape')
4444
if key in ('\r', '\n'):
4545
return 'enter'
46+
if key == '\x03':
47+
return 'escape'
4648
return key
4749
finally:
4850
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
@@ -67,7 +69,9 @@ def _render_selector(self, title, options, selected_index, help_text, default):
6769
style = 'bold green' if index == selected_index else ''
6870
table.add_row(marker, label, style=style)
6971

70-
footer = help_text or 'Use Up/Down arrows and Enter to select.'
72+
base_msg = 'Use Up/Down arrows and Enter to select.'
73+
cancel_msg = '(Esc/Ctrl-C to cancel)'
74+
footer = f"{help_text} {cancel_msg}" if help_text else f"{base_msg} {cancel_msg}"
7175
self.console.print(Panel.fit(footer, title='Interpreter TUI', border_style='green'))
7276
self.console.print(f"[bold cyan]{title}[/bold cyan]")
7377
self.console.print(table)
@@ -76,7 +80,7 @@ def _render_selector(self, title, options, selected_index, help_text, default):
7680
def _select_option(self, title, options, default, help_text=None):
7781
if not sys.stdin.isatty():
7882
default_choice = default if default in options else options[0]
79-
answer = Prompt.ask(f"{title}", default=default_choice).strip()
83+
answer = Prompt.ask(f"{title} \\[{'|'.join(options)}]", default=default_choice).strip()
8084
if answer in options:
8185
return answer
8286
for option in options:

0 commit comments

Comments
 (0)