fix(dashboard): auto-refresh the TUI every 1s
The main loop blocked on stdscr.getch() until the operator hit a key — a tool call landing in the queue while the operator was just watching wouldn't appear on the screen. The operator had to press any key to trigger a re-render and see the new proposal. Switch to stdscr.timeout(1000): getch returns -1 after 1s if no key was pressed, and the loop re-renders with the latest discover_pending() result. CPU cost is trivial; the loop body is ~one filesystem scan + curses draw per second. Also restructure status_line lifecycle: was cleared right after every render, which meant a timeout-driven re-render would wipe the message ~1s after the operator's keystroke set it. Now status_line is cleared only on actual key press, so messages like "approved cred-proxy-block for [dev-xyz]" persist until the operator does something else. Detail view + prompt view are unchanged — they're modal, the underlying proposal data doesn't move, and getstr can't tolerate a re-render mid-input. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -342,9 +342,17 @@ def _list_once() -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
_REFRESH_INTERVAL_MS = 1000
|
||||||
|
|
||||||
|
|
||||||
def _main_loop(stdscr: "curses._CursesWindow") -> None:
|
def _main_loop(stdscr: "curses._CursesWindow") -> None:
|
||||||
curses.curs_set(0)
|
curses.curs_set(0)
|
||||||
stdscr.nodelay(False)
|
# Auto-refresh: getch() returns -1 after the timeout if no key
|
||||||
|
# was pressed, so the loop re-renders with any newly-arrived
|
||||||
|
# proposals every ~1s. Without this the screen only updates
|
||||||
|
# when the operator hits a key — a tool call landing while the
|
||||||
|
# operator is just watching wouldn't appear.
|
||||||
|
stdscr.timeout(_REFRESH_INTERVAL_MS)
|
||||||
selected = 0
|
selected = 0
|
||||||
status_line = ""
|
status_line = ""
|
||||||
while True:
|
while True:
|
||||||
@@ -353,13 +361,22 @@ def _main_loop(stdscr: "curses._CursesWindow") -> None:
|
|||||||
selected = max(0, len(pending) - 1)
|
selected = max(0, len(pending) - 1)
|
||||||
|
|
||||||
_render(stdscr, pending, selected, status_line)
|
_render(stdscr, pending, selected, status_line)
|
||||||
status_line = ""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
key = stdscr.getch()
|
key = stdscr.getch()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if key == -1:
|
||||||
|
# Timeout fired — re-render with fresh queue. Status_line
|
||||||
|
# is left intact so messages from a prior keystroke stay
|
||||||
|
# readable until the operator actually does something else.
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Real keystroke: clear any stale status before dispatching
|
||||||
|
# so the next render reflects what just happened.
|
||||||
|
status_line = ""
|
||||||
|
|
||||||
if key in (ord("q"), 27): # q or ESC
|
if key in (ord("q"), 27): # q or ESC
|
||||||
return
|
return
|
||||||
if key == ord("e"):
|
if key == ord("e"):
|
||||||
|
|||||||
Reference in New Issue
Block a user