fix(tui): add ctrl+r reload and f2/ctrl+, settings key bindings
CI / unit_tests (pull_request) Failing after 12s
CI / helm (pull_request) Successful in 41s
CI / push-validation (pull_request) Successful in 33s
CI / build (pull_request) Successful in 3m51s
CI / lint (pull_request) Successful in 4m10s
CI / quality (pull_request) Successful in 4m34s
CI / typecheck (pull_request) Successful in 4m49s
CI / security (pull_request) Successful in 4m54s
CI / docker (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 7m2s
CI / integration_tests (pull_request) Successful in 7m58s
CI / coverage (pull_request) Successful in 16m40s
CI / status-check (pull_request) Failing after 3s
CI / unit_tests (pull_request) Failing after 12s
CI / helm (pull_request) Successful in 41s
CI / push-validation (pull_request) Successful in 33s
CI / build (pull_request) Successful in 3m51s
CI / lint (pull_request) Successful in 4m10s
CI / quality (pull_request) Successful in 4m34s
CI / typecheck (pull_request) Successful in 4m49s
CI / security (pull_request) Successful in 4m54s
CI / docker (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 7m2s
CI / integration_tests (pull_request) Successful in 7m58s
CI / coverage (pull_request) Successful in 16m40s
CI / status-check (pull_request) Failing after 3s
- Added ctrl+r (reload), f2 (settings), and ctrl+, (settings) to BINDINGS in src/cleveragents/tui/app.py - Implemented action_reload() to reset session transcript and refresh the persona bar - Implemented action_settings() to toggle the SettingsScreen overlay - Created new src/cleveragents/tui/widgets/settings_screen.py with SettingsScreen widget - Added SettingsScreen to compose() method and on_mount() initialization in app.py - Added Behave scenarios for ctrl+r reload and f2/ctrl+, settings in features/tui_app_coverage.feature - Added step definitions for the new scenarios in features/steps/tui_app_coverage_steps.py - Updated BINDINGS count from 3 to 6 in existing test scenario ISSUES CLOSED: #10475
This commit is contained in:
@@ -10,6 +10,8 @@ These steps target uncovered lines in cleveragents/tui/app.py:
|
||||
- Lines 131-142: _refresh_persona_bar method
|
||||
- Lines 144-185: on_input_submitted (all branches)
|
||||
- Line 189: CleverAgentsTuiApp alias
|
||||
- action_reload: ctrl+r binding
|
||||
- action_settings: f2 / ctrl+, binding
|
||||
"""
|
||||
|
||||
import importlib
|
||||
@@ -119,12 +121,14 @@ def _install_mock_textual(context):
|
||||
import cleveragents.tui.widgets.persona_bar as pb_mod
|
||||
import cleveragents.tui.widgets.prompt as prompt_mod
|
||||
import cleveragents.tui.widgets.reference_picker as rp_mod
|
||||
import cleveragents.tui.widgets.settings_screen as ss_mod
|
||||
import cleveragents.tui.widgets.slash_command_overlay as sco_mod
|
||||
|
||||
importlib.reload(hp_mod)
|
||||
importlib.reload(pb_mod)
|
||||
importlib.reload(prompt_mod)
|
||||
importlib.reload(rp_mod)
|
||||
importlib.reload(ss_mod)
|
||||
importlib.reload(sco_mod)
|
||||
|
||||
import cleveragents.tui.app as app_mod
|
||||
@@ -147,12 +151,14 @@ def _restore_modules(context):
|
||||
import cleveragents.tui.widgets.persona_bar as pb_mod
|
||||
import cleveragents.tui.widgets.prompt as prompt_mod
|
||||
import cleveragents.tui.widgets.reference_picker as rp_mod
|
||||
import cleveragents.tui.widgets.settings_screen as ss_mod
|
||||
import cleveragents.tui.widgets.slash_command_overlay as sco_mod
|
||||
|
||||
importlib.reload(hp_mod)
|
||||
importlib.reload(pb_mod)
|
||||
importlib.reload(prompt_mod)
|
||||
importlib.reload(rp_mod)
|
||||
importlib.reload(ss_mod)
|
||||
importlib.reload(sco_mod)
|
||||
|
||||
import cleveragents.tui.app as app_mod
|
||||
@@ -510,3 +516,84 @@ def step_alias_check(context):
|
||||
assert (
|
||||
context._tui_app_mod.CleverAgentsTuiApp.__name__ == "_TextualCleverAgentsTuiApp"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# action_reload (ctrl+r binding)
|
||||
# ---------------------------------------------------------------------------
|
||||
@when("I call action_reload on the app")
|
||||
def step_call_action_reload(context):
|
||||
context._tui_app.action_reload()
|
||||
|
||||
|
||||
@then("the session transcript should be empty after reload")
|
||||
def step_session_transcript_empty(context):
|
||||
assert context._tui_app._session.transcript == []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# action_settings (f2 / ctrl+, binding)
|
||||
# ---------------------------------------------------------------------------
|
||||
@when("I call action_settings on the app")
|
||||
def step_call_action_settings(context):
|
||||
context._tui_app.action_settings()
|
||||
|
||||
|
||||
@then("the settings screen should be visible")
|
||||
def step_settings_screen_visible(context):
|
||||
from cleveragents.tui.widgets.settings_screen import SettingsScreen
|
||||
|
||||
settings = context._tui_app.query_one("#settings-screen", SettingsScreen)
|
||||
assert settings.visible is True
|
||||
|
||||
|
||||
@then("the settings screen should be hidden")
|
||||
def step_settings_screen_hidden(context):
|
||||
from cleveragents.tui.widgets.settings_screen import SettingsScreen
|
||||
|
||||
settings = context._tui_app.query_one("#settings-screen", SettingsScreen)
|
||||
assert settings.visible is False
|
||||
|
||||
|
||||
@then('the settings screen text should contain "{text}"')
|
||||
def step_settings_screen_contains(context, text):
|
||||
from cleveragents.tui.widgets.settings_screen import SettingsScreen
|
||||
|
||||
settings = context._tui_app.query_one("#settings-screen", SettingsScreen)
|
||||
assert text in settings._text, f"Expected '{text}' in '{settings._text}'"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# BINDINGS assertions
|
||||
# ---------------------------------------------------------------------------
|
||||
@then("the app bindings should include ctrl+r for reload")
|
||||
def step_bindings_include_ctrl_r(context):
|
||||
bindings = context._tui_app.BINDINGS
|
||||
keys = [b[0] for b in bindings]
|
||||
assert "ctrl+r" in keys, f"ctrl+r not found in bindings: {keys}"
|
||||
reload_binding = next(b for b in bindings if b[0] == "ctrl+r")
|
||||
assert reload_binding[1] == "reload", (
|
||||
f"Expected action 'reload', got '{reload_binding[1]}'"
|
||||
)
|
||||
|
||||
|
||||
@then("the app bindings should include f2 for settings")
|
||||
def step_bindings_include_f2(context):
|
||||
bindings = context._tui_app.BINDINGS
|
||||
keys = [b[0] for b in bindings]
|
||||
assert "f2" in keys, f"f2 not found in bindings: {keys}"
|
||||
settings_binding = next(b for b in bindings if b[0] == "f2")
|
||||
assert settings_binding[1] == "settings", (
|
||||
f"Expected action 'settings', got '{settings_binding[1]}'"
|
||||
)
|
||||
|
||||
|
||||
@then("the app bindings should include ctrl+comma for settings")
|
||||
def step_bindings_include_ctrl_comma(context):
|
||||
bindings = context._tui_app.BINDINGS
|
||||
keys = [b[0] for b in bindings]
|
||||
assert "ctrl+comma" in keys, f"ctrl+comma not found in bindings: {keys}"
|
||||
settings_binding = next(b for b in bindings if b[0] == "ctrl+comma")
|
||||
assert settings_binding[1] == "settings", (
|
||||
f"Expected action 'settings', got '{settings_binding[1]}'"
|
||||
)
|
||||
|
||||
@@ -41,13 +41,13 @@ Feature: TUI App Coverage
|
||||
And the app should store the command router
|
||||
And the app should store the persona state
|
||||
|
||||
# --- CSS_PATH and BINDINGS class variables (lines 84-89) ---
|
||||
# --- CSS_PATH and BINDINGS class variables ---
|
||||
|
||||
Scenario: The Textual TUI app has correct class variables
|
||||
Given a mock command router and persona state
|
||||
When I instantiate the Textual TUI app
|
||||
Then the app class should have CSS_PATH set to "cleveragents.tcss"
|
||||
And the app class should have 3 key bindings
|
||||
And the app class should have 6 key bindings
|
||||
|
||||
# --- compose method (lines 102-112) ---
|
||||
|
||||
@@ -196,3 +196,41 @@ Feature: TUI App Coverage
|
||||
And I call on_mount on the app
|
||||
And I submit "!true" to the app
|
||||
Then the conversation widget should contain "(empty output)"
|
||||
|
||||
# --- action_reload method (ctrl+r binding) ---
|
||||
|
||||
Scenario: action_reload clears the session transcript and refreshes the bar
|
||||
Given a mock command router and persona state
|
||||
When I instantiate the Textual TUI app
|
||||
And I call on_mount on the app
|
||||
And I call action_reload on the app
|
||||
Then the session transcript should be empty after reload
|
||||
And the persona bar content should be refreshed
|
||||
|
||||
# --- action_settings method (f2 / ctrl+, binding) ---
|
||||
|
||||
Scenario: action_settings opens the settings screen
|
||||
Given a mock command router and persona state
|
||||
When I instantiate the Textual TUI app
|
||||
And I call on_mount on the app
|
||||
And I call action_settings on the app
|
||||
Then the settings screen should be visible
|
||||
And the settings screen text should contain "Settings"
|
||||
|
||||
Scenario: action_settings toggles the settings screen closed
|
||||
Given a mock command router and persona state
|
||||
When I instantiate the Textual TUI app
|
||||
And I call on_mount on the app
|
||||
And I call action_settings on the app
|
||||
And I call action_settings on the app
|
||||
Then the settings screen should be hidden
|
||||
|
||||
# --- BINDINGS class variable updated count ---
|
||||
|
||||
Scenario: The Textual TUI app has 6 key bindings including reload and settings
|
||||
Given a mock command router and persona state
|
||||
When I instantiate the Textual TUI app
|
||||
Then the app class should have 6 key bindings
|
||||
And the app bindings should include ctrl+r for reload
|
||||
And the app bindings should include f2 for settings
|
||||
And the app bindings should include ctrl+comma for settings
|
||||
|
||||
@@ -20,6 +20,7 @@ from cleveragents.tui.widgets.help_panel_overlay import (
|
||||
from cleveragents.tui.widgets.persona_bar import PersonaBar
|
||||
from cleveragents.tui.widgets.prompt import PromptInput
|
||||
from cleveragents.tui.widgets.reference_picker import ReferencePickerOverlay
|
||||
from cleveragents.tui.widgets.settings_screen import SettingsScreen
|
||||
from cleveragents.tui.widgets.slash_command_overlay import SlashCommandOverlay
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -93,6 +94,9 @@ if _TEXTUAL_AVAILABLE:
|
||||
("ctrl+q", "quit", "Quit"),
|
||||
("f1", "help", "Help"),
|
||||
("ctrl+t", "cycle_preset", "Cycle Preset"),
|
||||
("ctrl+r", "reload", "Reload"),
|
||||
("f2", "settings", "Settings"),
|
||||
("ctrl+comma", "settings", "Settings"),
|
||||
]
|
||||
|
||||
def __init__(
|
||||
@@ -114,6 +118,7 @@ if _TEXTUAL_AVAILABLE:
|
||||
yield ReferencePickerOverlay(id="reference-picker")
|
||||
yield SlashCommandOverlay(id="slash-overlay")
|
||||
yield ActorSelectionOverlay(id="actor-selection")
|
||||
yield SettingsScreen(id="settings-screen")
|
||||
yield PromptInput(
|
||||
placeholder="Type message, /command, or !shell ...", id="prompt"
|
||||
)
|
||||
@@ -132,6 +137,8 @@ if _TEXTUAL_AVAILABLE:
|
||||
slash = self.query_one("#slash-overlay", SlashCommandOverlay)
|
||||
slash.set_commands("", slash_command_specs())
|
||||
actor_overlay = self.query_one("#actor-selection", ActorSelectionOverlay)
|
||||
settings = self.query_one("#settings-screen", SettingsScreen)
|
||||
settings.hide()
|
||||
if first_run:
|
||||
actor_overlay.show()
|
||||
else:
|
||||
@@ -152,6 +159,18 @@ if _TEXTUAL_AVAILABLE:
|
||||
self._persona_state.cycle_preset(self._session.session_id)
|
||||
self._refresh_persona_bar()
|
||||
|
||||
def action_reload(self) -> None:
|
||||
"""Reload/refresh the current session or conversation."""
|
||||
self._session = SessionView(
|
||||
session_id=self._session.session_id, transcript=[]
|
||||
)
|
||||
self._refresh_persona_bar()
|
||||
|
||||
def action_settings(self) -> None:
|
||||
"""Open or close the Settings screen."""
|
||||
settings = self.query_one("#settings-screen", SettingsScreen)
|
||||
settings.toggle()
|
||||
|
||||
def _refresh_persona_bar(self) -> None:
|
||||
persona = self._persona_state.active_persona(self._session.session_id)
|
||||
preset = self._persona_state.current_preset(self._session.session_id)
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
"""Settings screen overlay for the TUI."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
from typing import Any
|
||||
|
||||
|
||||
def _load_static_base() -> type[Any]:
|
||||
try:
|
||||
return importlib.import_module("textual.widgets").Static
|
||||
except Exception: # pragma: no cover - optional dependency
|
||||
|
||||
class _FallbackStatic:
|
||||
def __init__(self, *args: object, **kwargs: object) -> None:
|
||||
self._text = ""
|
||||
|
||||
def update(self, text: str) -> None:
|
||||
self._text = text
|
||||
|
||||
return _FallbackStatic
|
||||
|
||||
|
||||
_StaticBase = _load_static_base()
|
||||
|
||||
_SETTINGS_CONTENT = (
|
||||
"Settings\n"
|
||||
"\n"
|
||||
" Theme Light / Dark / Auto\n"
|
||||
" Font size Small / Medium / Large\n"
|
||||
" Wrap width 80 / 100 / 120 / Off\n"
|
||||
" Timestamps On / Off\n"
|
||||
" Compact mode On / Off\n"
|
||||
"\n"
|
||||
"─────────────────────────────────────────────────────\n"
|
||||
"escape Close settings\n"
|
||||
)
|
||||
|
||||
|
||||
class SettingsScreen(_StaticBase):
|
||||
"""Overlay that displays application settings.
|
||||
|
||||
Opened via the ``f2`` or ``ctrl+,`` key bindings. Dismissed with
|
||||
``escape``.
|
||||
"""
|
||||
|
||||
def __init__(self, *args: object, **kwargs: object) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self._visible: bool = False
|
||||
self._text: str = ""
|
||||
|
||||
@property
|
||||
def visible(self) -> bool:
|
||||
"""Return whether the settings screen is currently visible."""
|
||||
return self._visible
|
||||
|
||||
def show(self) -> None:
|
||||
"""Display the settings screen."""
|
||||
self._visible = True
|
||||
self._text = _SETTINGS_CONTENT
|
||||
self.update(self._text)
|
||||
|
||||
def hide(self) -> None:
|
||||
"""Hide the settings screen and clear its content."""
|
||||
self._visible = False
|
||||
self._text = ""
|
||||
self.update("")
|
||||
|
||||
def toggle(self) -> bool:
|
||||
"""Toggle the settings screen visibility.
|
||||
|
||||
Returns ``True`` when the screen ends visible and ``False`` when hidden.
|
||||
"""
|
||||
if self._visible:
|
||||
self.hide()
|
||||
return False
|
||||
self.show()
|
||||
return True
|
||||
Reference in New Issue
Block a user