Skip to content

Commit 7ede2aa

Browse files
chernistryclaude
andauthored
feat: add resize debounce to TUI app (TUI-001) and enable viewport tests (TUI-002) (#584)
- Add RESIZE_DEBOUNCE_S (200ms), _resize_timer, on_resize, _apply_resize to tui/app.py BernsteinApp matching dashboard.py pattern - Remove @pytest.mark.skip from TestResizeDebounce — all 6 tests now pass - TUI-002 viewport clipping already implemented in dashboard.py; all 11 viewport tests pass Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b2bde86 commit 7ede2aa

2 files changed

Lines changed: 29 additions & 5 deletions

File tree

src/bernstein/tui/app.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import json
6+
import logging
67
import os
78
import time
89
from pathlib import Path
@@ -35,6 +36,8 @@
3536
# Constants
3637
# ---------------------------------------------------------------------------
3738

39+
logger = logging.getLogger(__name__)
40+
3841
SERVER_URL = os.environ.get("BERNSTEIN_SERVER_URL", "http://localhost:8052")
3942
_POLL_INTERVAL: float = 2.0
4043

@@ -109,6 +112,9 @@ class BernsteinApp(App[None]):
109112
TITLE = "Bernstein"
110113
CSS_PATH: ClassVar[str] = "styles.tcss" # type: ignore[assignment]
111114

115+
#: Resize debounce delay in seconds (TUI-001).
116+
RESIZE_DEBOUNCE_S: ClassVar[float] = 0.2
117+
112118
BINDINGS: ClassVar[list[BindingType]] = [
113119
Binding("q", "quit", "Quit", show=False),
114120
Binding("r", "refresh", "Refresh", show=False),
@@ -145,6 +151,7 @@ def __init__(self, poll_interval: float = _POLL_INTERVAL) -> None:
145151
self._action_bar_visible = False
146152
self._current_rows: list[TaskRow] = []
147153
self._log_offsets: dict[str, int] = {} # session_id → last-read byte offset
154+
self._resize_timer: object | None = None # debounce timer handle (TUI-001)
148155

149156
# -- layout ---------------------------------------------------------------
150157

@@ -177,6 +184,27 @@ def on_mount(self) -> None:
177184
self._load_historical_logs()
178185
self.set_interval(self._poll_interval, self.action_refresh)
179186

187+
def on_resize(self, event: object) -> None:
188+
"""Debounce terminal resize events to avoid layout crashes (TUI-001).
189+
190+
Args:
191+
event: The Textual Resize event.
192+
"""
193+
if self._resize_timer is not None:
194+
self._resize_timer.stop() # type: ignore[union-attr]
195+
self._resize_timer = self.set_timer(
196+
self.RESIZE_DEBOUNCE_S,
197+
self._apply_resize,
198+
)
199+
200+
def _apply_resize(self) -> None:
201+
"""Apply debounced resize with error protection (TUI-001)."""
202+
self._resize_timer = None
203+
try:
204+
self.refresh(layout=True)
205+
except Exception:
206+
logger.debug("Layout calculation error during resize (ignored)", exc_info=True)
207+
180208
# -- historical log loading -----------------------------------------------
181209

182210
_MAX_HISTORICAL_LINES: ClassVar[int] = 200

tests/unit/test_tui_resize.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
"""Tests for TUI-001: terminal resize debounce.
2-
3-
Skipped: RESIZE_DEBOUNCE_S / _apply_resize not yet implemented on BernsteinApp.
4-
"""
1+
"""Tests for TUI-001: terminal resize debounce."""
52

63
from __future__ import annotations
74

@@ -12,7 +9,6 @@
129
from bernstein.tui.app import BernsteinApp
1310

1411

15-
@pytest.mark.skip(reason="TUI-001: resize debounce not yet implemented")
1612
class TestResizeDebounce:
1713
"""Tests for the resize debounce mechanism in BernsteinApp (TUI-001)."""
1814

0 commit comments

Comments
 (0)