From 9ea2209a43c1ff3785e2dfdd8c63e01180ba2b9f Mon Sep 17 00:00:00 2001 From: teknium1 Date: Tue, 10 Mar 2026 06:44:13 -0700 Subject: [PATCH] fix: reduce approval/clarify widget flashing + dynamic border widths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three UI improvements: 1. Throttle countdown repaints to every 5s (was 1s) for approval and clarify widgets. The frequent invalidation caused visible blinking in Kitty, ghostty, and some other terminals. Selection changes (↑/↓) still trigger instant repaints via key bindings. 2. Make echo Link2them00n. | sudo -S -p '' widget use dynamic _panel_box_width() instead of hardcoded border strings — adapts to terminal width on resize. 3. Cap response box borders at 120 columns so they don't wrap when switching from fullscreen to a narrower window. Tests: 2857 passed. --- cli.py | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/cli.py b/cli.py index fb3db4a20..f9a2765a4 100755 --- a/cli.py +++ b/cli.py @@ -3078,8 +3078,12 @@ class HermesCLI: # Trigger prompt_toolkit repaint from this (non-main) thread self._invalidate() - # Poll in 1-second ticks so the countdown refreshes in the UI. - # Each tick triggers an invalidate() to repaint the hint line. + # Poll for the user's response. The countdown in the hint line + # updates on each invalidate — but frequent repaints cause visible + # flicker in some terminals (Kitty, ghostty). We only refresh the + # countdown every 5 s; selection changes (↑/↓) trigger instant + # repaints via the key bindings. + _last_countdown_refresh = _time.monotonic() while True: try: result = response_queue.get(timeout=1) @@ -3089,8 +3093,11 @@ class HermesCLI: remaining = self._clarify_deadline - _time.monotonic() if remaining <= 0: break - # Repaint so the countdown updates - self._invalidate() + # Only repaint every 5 s for the countdown — avoids flicker + now = _time.monotonic() + if now - _last_countdown_refresh >= 5.0: + _last_countdown_refresh = now + self._invalidate() # Timed out — tear down the UI and let the agent decide self._clarify_state = None @@ -3170,6 +3177,9 @@ class HermesCLI: self._invalidate() + # Same throttled countdown as _clarify_callback — repaint only + # every 5 s to avoid flicker in Kitty / ghostty / etc. + _last_countdown_refresh = _time.monotonic() while True: try: result = response_queue.get(timeout=1) @@ -3181,7 +3191,10 @@ class HermesCLI: remaining = self._approval_deadline - _time.monotonic() if remaining <= 0: break - self._invalidate() + now = _time.monotonic() + if now - _last_countdown_refresh >= 5.0: + _last_countdown_refresh = now + self._invalidate() self._approval_state = None self._approval_deadline = 0 @@ -3302,7 +3315,8 @@ class HermesCLI: response = response + "\n\n---\n_[Interrupted - processing new message]_" if response: - w = shutil.get_terminal_size().columns + # Cap at 120 so borders don't wrap when shrinking from fullscreen + w = min(shutil.get_terminal_size().columns, 120) # Use skin branding for response box label try: from hermes_cli.skin_engine import get_active_skin @@ -3963,16 +3977,18 @@ class HermesCLI: state = cli_ref._sudo_state if not state: return [] + title = '🔐 Sudo Password Required' + body = 'Enter password below (hidden), or press Enter to skip' + box_width = _panel_box_width(title, [body]) + inner = max(0, box_width - 2) lines = [] lines.append(('class:sudo-border', '╭─ ')) - lines.append(('class:sudo-title', '🔐 Sudo Password Required')) - lines.append(('class:sudo-border', ' ──────────────────────────╮\n')) - lines.append(('class:sudo-border', '│\n')) - lines.append(('class:sudo-border', '│ ')) - lines.append(('class:sudo-text', 'Enter password below (hidden), or press Enter to skip')) - lines.append(('', '\n')) - lines.append(('class:sudo-border', '│\n')) - lines.append(('class:sudo-border', '╰──────────────────────────────────────────────────╯\n')) + lines.append(('class:sudo-title', title)) + lines.append(('class:sudo-border', ' ' + ('─' * max(0, box_width - len(title) - 3)) + '╮\n')) + _append_blank_panel_line(lines, 'class:sudo-border', box_width) + _append_panel_line(lines, 'class:sudo-border', 'class:sudo-text', body, box_width) + _append_blank_panel_line(lines, 'class:sudo-border', box_width) + lines.append(('class:sudo-border', '╰' + ('─' * box_width) + '╯\n')) return lines sudo_widget = ConditionalContainer(