diff --git a/cli.py b/cli.py index 9855327e9..2709b905a 100644 --- a/cli.py +++ b/cli.py @@ -4520,16 +4520,18 @@ class HermesCLI: scroll_offset: int, n: int, term_rows: int, - chrome_reserve: int = 14, + reserved_below: int = 6, + panel_chrome: int = 6, + min_visible: int = 3, ) -> tuple[int, int]: - """Resolve (scroll_offset, visible_count) for the /model picker panel. + """Resolve (scroll_offset, visible) for the /model picker viewport. - ``term_rows - chrome_reserve`` caps how many rows the panel may use for - items; when the list overflows we slide the offset to keep ``selected`` - on screen. The position counter sits in the bottom border, so no extra - row is reserved for it. + ``reserved_below`` matches the approval / clarify panels — input area, + status bar, and separators below the panel. ``panel_chrome`` covers + this panel's own borders + blanks + hint row. The remaining rows hold + the scrollable list, with the offset slid to keep ``selected`` on screen. """ - max_visible = max(3, term_rows - chrome_reserve) + max_visible = max(min_visible, term_rows - reserved_below - panel_chrome) if n <= max_visible: return 0, n visible = max_visible @@ -9537,7 +9539,7 @@ class HermesCLI: from prompt_toolkit.application import get_app term_rows = get_app().output.get_size().rows except Exception: - term_rows = shutil.get_terminal_size((80, 24)).lines + term_rows = shutil.get_terminal_size((100, 24)).lines scroll_offset, visible = HermesCLI._compute_model_picker_viewport( selected, state.get("_scroll_offset", 0), len(choices), term_rows, ) diff --git a/tests/hermes_cli/test_model_picker_viewport.py b/tests/hermes_cli/test_model_picker_viewport.py index 161d15eed..4f56ee804 100644 --- a/tests/hermes_cli/test_model_picker_viewport.py +++ b/tests/hermes_cli/test_model_picker_viewport.py @@ -19,16 +19,15 @@ class TestPickerViewport: assert visible == 5 def test_long_list_caps_visible_to_chrome_budget(self): - # 36 models, 30 terminal rows, chrome_reserve=14 → max_visible=16. - # Position counter lives in the bottom border, no row reserved. + # 30 rows minus reserved_below=6 minus panel_chrome=6 → max_visible=18. offset, visible = _compute(selected=0, scroll_offset=0, n=36, term_rows=30) - assert visible == 16 + assert visible == 18 assert offset == 0 def test_cursor_past_window_scrolls_down(self): - offset, visible = _compute(selected=20, scroll_offset=0, n=36, term_rows=30) - assert visible == 16 - assert 20 in range(offset, offset + visible) + offset, visible = _compute(selected=22, scroll_offset=0, n=36, term_rows=30) + assert visible == 18 + assert 22 in range(offset, offset + visible) def test_cursor_above_window_scrolls_up(self): offset, visible = _compute(selected=3, scroll_offset=15, n=36, term_rows=30) @@ -36,16 +35,16 @@ class TestPickerViewport: assert 3 in range(offset, offset + visible) def test_offset_clamped_to_bottom(self): - # Selected on last item — offset must keep visible window full, not - # walk past the end of the list. + # Selected on the last item — offset must keep the visible window + # full, not walk past the end of the list. offset, visible = _compute(selected=35, scroll_offset=0, n=36, term_rows=30) assert offset + visible == 36 assert 35 in range(offset, offset + visible) def test_tiny_terminal_uses_minimum_visible(self): - # term_rows below chrome_reserve falls back to the floor of 3 rows. + # term_rows below the chrome budget falls back to the floor of 3 rows. _, visible = _compute(selected=0, scroll_offset=0, n=20, term_rows=10) - assert visible == 3 # max(3, 10 - 14) == 3 + assert visible == 3 def test_offset_recovers_after_stage_switch(self): # When the user backs out of the model stage and re-enters with