fix(tui): add on_input_changed handler for live overlay updates #10918

Merged
HAL9000 merged 2 commits from bugfix/m8-tui-on-input-changed into master 2026-06-10 14:35:42 +00:00
3 changed files with 128 additions and 0 deletions
+57
View File
@@ -626,3 +626,60 @@ def step_theme_class_var(context, theme: str) -> None:
assert theme == context._tui_app.THEME, (
f"Expected THEME='{theme}', got '{context._tui_app.THEME}'"
)
# ---------------------------------------------------------------------------
# on_input_changed: live overlay update steps
# ---------------------------------------------------------------------------
def _trigger_input_changed(context, text: str) -> None:
"""Set prompt text and fire on_input_changed."""
from cleveragents.tui.widgets.prompt import PromptInput
prompt = context._tui_app.query_one("#prompt", PromptInput)
prompt.text = text
event = SimpleNamespace()
context._tui_app.on_input_changed(event)
@when('I trigger on_input_changed with text "{text}"')
def step_trigger_input_changed(context, text: str) -> None:
_trigger_input_changed(context, text)
@when("I trigger on_input_changed with empty text")
def step_trigger_input_changed_empty_text(context) -> None:
_trigger_input_changed(context, "")
@then('the slash overlay should show only commands matching "{query}"')
def step_slash_overlay_filtered(context, query: str) -> None:
from cleveragents.tui.widgets.slash_command_overlay import SlashCommandOverlay
overlay = context._tui_app.query_one("#slash-overlay", SlashCommandOverlay)
assert overlay._text, "Slash overlay text should not be empty"
# All displayed commands must start with the query
for cmd in overlay._commands:
assert cmd.command.startswith(query), (
f"Command '{cmd.command}' does not start with '{query}'"
)
@then("the reference picker should be reset to empty")
def step_ref_picker_reset(context) -> None:
from cleveragents.tui.widgets.reference_picker import ReferencePickerOverlay
picker = context._tui_app.query_one("#reference-picker", ReferencePickerOverlay)
assert picker._text == "", (
f"Expected reference picker to be empty after reset, got: '{picker._text}'"
)
@then("the slash overlay should be reset to all commands")
def step_slash_overlay_all_commands(context) -> None:
from cleveragents.tui.widgets.slash_command_overlay import SlashCommandOverlay
overlay = context._tui_app.query_one("#slash-overlay", SlashCommandOverlay)
# "reset to all commands" means the overlay returned to its default hidden state
# (set_commands with empty query calls hide(), clearing _text and _commands)
assert not overlay._visible, "Slash overlay should be hidden after reset"
assert overlay._text == "", "Slash overlay text should be empty after reset"
+41
View File
@@ -228,3 +228,44 @@ Feature: TUI App Coverage
When I call action_escape on the app
Then the reference picker overlay should be hidden
And the prompt text should be "draft"
# --- on_input_changed handler (live overlay updates) ---
Scenario: on_input_changed with slash prefix filters slash commands live
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 trigger on_input_changed with text "/ses"
Then the slash overlay should show only commands matching "ses"
And the reference picker should be reset to empty
Scenario: on_input_changed with at-sign updates reference picker live
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 trigger on_input_changed with text "@proj"
Then the reference picker should have been updated
And the slash overlay should be reset to all commands
Scenario: on_input_changed with plain text resets both overlays
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 trigger on_input_changed with text "hello world"
Then the slash overlay should be reset to all commands
And the reference picker should be reset to empty
Scenario: on_input_changed with empty at-sign resets reference picker
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 trigger on_input_changed with text "send @"
Then the reference picker should be reset to empty
Scenario: on_input_changed with empty text resets both overlays
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 trigger on_input_changed with empty text
Then the slash overlay should be reset to all commands
And the reference picker should be reset to empty
+30
View File
@@ -434,6 +434,36 @@ if _TEXTUAL_AVAILABLE:
)
conversation.update(text)
def on_input_changed(self, event: InputSubmittedEvent) -> None:
"""Update overlays live as the user types.
- When text starts with ``/``, filter slash commands by the query
after the slash so the overlay narrows in real time.
- When text contains ``@``, extract the token after the last ``@``
and update the reference picker with matching suggestions.
- Otherwise reset both overlays to their default (unfiltered) state.
"""
del event
prompt = self.query_one("#prompt", PromptInput)
text = prompt.text
slash = self.query_one("#slash-overlay", SlashCommandOverlay)
ref_picker = self.query_one("#reference-picker", ReferencePickerOverlay)
stripped = text.lstrip()
if stripped.startswith("/"):
query = stripped[1:]
slash.set_commands(query, slash_command_specs())
ref_picker.set_suggestions("", [])
elif "@" in text:
at_index = text.rfind("@")
tail = text[at_index + 1 :]
tokens = tail.split()
query = tokens[0] if tokens else ""
ref_picker.set_suggestions(query, suggestions(query))
slash.set_commands("", slash_command_specs())
else:
slash.set_commands("", slash_command_specs())
ref_picker.set_suggestions("", [])
def on_input_submitted(self, event: InputSubmittedEvent) -> None:
del event
prompt = self.query_one("#prompt", PromptInput)