fix: robust terminal size detection + resize handling

- Try ioctl on stdin/stdout/stderr, then /dev/tty, then os/shutil
- Add SIGWINCH handler to re-render on terminal resize
- Clamp scroll position after resize

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-13 13:37:16 +02:00
parent b67cf4c1bd
commit 365c9118bf

View File

@@ -495,11 +495,36 @@ def fetch_task_content(nid):
# ── Terminal helpers ──────────────────────────────────────────────── # ── Terminal helpers ────────────────────────────────────────────────
def get_terminal_size(): def get_terminal_size():
"""Get terminal rows, cols using multiple methods."""
# Try ioctl on multiple fds
for fd_try in [sys.stdout, sys.stdin, sys.stderr]:
try:
result = struct.unpack('hh', fcntl.ioctl(fd_try, termios.TIOCGWINSZ, b'\0' * 4))
if result[0] > 0 and result[1] > 0:
return result[0], result[1]
except Exception:
pass
# Try /dev/tty directly
try: try:
result = struct.unpack('hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, b'\0' * 4)) with open('/dev/tty', 'r') as tty_fd:
return result[0], result[1] # rows, cols result = struct.unpack('hh', fcntl.ioctl(tty_fd, termios.TIOCGWINSZ, b'\0' * 4))
if result[0] > 0 and result[1] > 0:
return result[0], result[1]
except Exception: except Exception:
return 40, 80 pass
# Try os/shutil
try:
sz = os.get_terminal_size()
return sz.lines, sz.columns
except Exception:
pass
try:
import shutil
sz = shutil.get_terminal_size()
return sz.lines, sz.columns
except Exception:
pass
return 40, 80
def read_key(fd): def read_key(fd):
@@ -546,6 +571,12 @@ def tui_viewer(range_days, client):
input_mode = False input_mode = False
input_buf = "" input_buf = ""
status_msg = "" status_msg = ""
needs_redraw = [False] # mutable for signal handler
# Handle terminal resize
def on_resize(signum, frame):
needs_redraw[0] = True
signal.signal(signal.SIGWINCH, on_resize)
def split_lines(content): def split_lines(content):
return content.split('\n') return content.split('\n')
@@ -626,11 +657,17 @@ def tui_viewer(range_days, client):
load_briefing() load_briefing()
while True: while True:
if needs_redraw[0]:
needs_redraw[0] = False
render() render()
key = read_key(fd) key = read_key(fd)
if not key:
continue
rows, cols = get_terminal_size() rows, cols = get_terminal_size()
view_height = rows - 1 view_height = rows - 1
max_scroll = max(0, len(lines) - view_height) max_scroll = max(0, len(lines) - view_height)
# Clamp scroll after resize
scroll = min(scroll, max_scroll)
if input_mode: if input_mode:
if key == 'ESC': if key == 'ESC':