fix(tui): show command descriptions in SlashCommandOverlay alongside command names
CI / lint (pull_request) Successful in 20s
CI / typecheck (pull_request) Successful in 48s
CI / quality (pull_request) Successful in 37s
CI / security (pull_request) Successful in 59s
CI / build (pull_request) Successful in 38s
CI / helm (pull_request) Successful in 24s
CI / unit_tests (pull_request) Failing after 7m1s
CI / docker (pull_request) Has been skipped
CI / e2e_tests (pull_request) Successful in 16m16s
CI / integration_tests (pull_request) Failing after 23m13s
CI / coverage (pull_request) Successful in 11m18s
CI / status-check (pull_request) Failing after 2s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Successful in 56m54s

Update SlashCommandOverlay.set_commands() to accept list[SlashCommandSpec]
instead of list[str], and render each entry as aligned columns:
  /command-name       Description text

Changes:
- slash_command_overlay.py: update set_commands() signature to accept
  list[SlashCommandSpec]; render name + description with aligned padding
  using _COMMAND_COL_WIDTH = 28 characters for the name column
- slash_catalog.py: add slash_command_specs() helper returning all specs
- app.py: switch on_mount() to call slash_command_specs() instead of
  slash_command_names() so full spec objects are passed to the overlay
- features/tui_slash_command_overlay_coverage.feature: add scenario
  verifying description rendering; update existing scenarios to use
  SlashCommandSpec objects via _make_specs() helper
- features/steps/tui_slash_command_overlay_coverage_steps.py: update
  step implementations to build SlashCommandSpec objects from CSV names
- features/tui_slash_overlay_descriptions.feature: new feature file with
  BDD scenarios covering description rendering and @tdd_expected_fail
  capture of the pre-fix name-only behaviour
- features/steps/tui_slash_overlay_descriptions_steps.py: step defs for
  the new feature file
- robot/tui_smoke.robot: add Slash Command Overlay Renders Descriptions
  integration test verifying descriptions appear in rendered overlay

ISSUES CLOSED: #3437
This commit is contained in:
2026-04-05 17:50:51 +00:00
parent 1783f0a211
commit 0f9ca00ce7
8 changed files with 144 additions and 17 deletions
@@ -5,7 +5,7 @@ These steps target uncovered lines in slash_command_overlay.py:
- Line 19: _FallbackStatic.update sets self._text = text
- Lines 31-32: set_commands filtering logic (query vs empty query)
- Line 34: lines = [f"/{query}"]
- Lines 35-36: loop appending filtered commands
- Lines 35-36: loop appending filtered commands with descriptions
- Lines 37-38: "(no commands)" fallback when nothing matches
- Line 39: self.update("\n".join(lines))
"""
@@ -16,9 +16,21 @@ from unittest.mock import patch
from behave import given, then, when
import cleveragents.tui.widgets.slash_command_overlay as _sco_mod
from cleveragents.tui.slash_catalog import SlashCommandSpec
from cleveragents.tui.widgets.slash_command_overlay import _load_static_base
def _make_specs(names_csv: str) -> list[SlashCommandSpec]:
"""Build a list of SlashCommandSpec from a CSV of command names."""
return [
SlashCommandSpec(
command=c.strip(), group="Test", description=f"Desc for {c.strip()}"
)
for c in names_csv.split(",")
if c.strip()
]
# ---------------------------------------------------------------------------
# Background — force FallbackStatic so tests never need a live Textual app
# ---------------------------------------------------------------------------
@@ -81,16 +93,16 @@ def step_verify_text(context, expected):
# ---------------------------------------------------------------------------
@when('I call set_commands with query "{query}" and commands "{commands_csv}"')
def step_call_set_commands(context, query, commands_csv):
"""Call set_commands with a query and a CSV list of commands."""
commands = [c.strip() for c in commands_csv.split(",") if c.strip()]
context.overlay.set_commands(query, commands)
"""Call set_commands with a query and a CSV list of commands (as SlashCommandSpec)."""
specs = _make_specs(commands_csv)
context.overlay.set_commands(query, specs)
@when('I call set_commands with empty query and commands "{commands_csv}"')
def step_call_set_commands_empty_query(context, commands_csv):
"""Call set_commands with an empty query and a CSV list of commands."""
commands = [c.strip() for c in commands_csv.split(",") if c.strip()]
context.overlay.set_commands("", commands)
"""Call set_commands with an empty query and a CSV list of commands (as SlashCommandSpec)."""
specs = _make_specs(commands_csv)
context.overlay.set_commands("", specs)
@then('the overlay text should contain "{substring}"')
@@ -128,9 +140,12 @@ def step_text_not_contains(context, substring):
'I call set_commands with query "" and {count:d} commands prefixed with "{prefix}"'
)
def step_call_set_commands_many(context, count, prefix):
"""Call set_commands with a large number of commands."""
commands = [f"{prefix}{i}" for i in range(count)]
context.overlay.set_commands("", commands)
"""Call set_commands with a large number of commands (as SlashCommandSpec)."""
specs = [
SlashCommandSpec(command=f"{prefix}{i}", group="Test", description=f"Desc {i}")
for i in range(count)
]
context.overlay.set_commands("", specs)
@then("the overlay text should have exactly {expected:d} lines")
@@ -0,0 +1,29 @@
"""Step definitions for tui_slash_overlay_descriptions.feature.
These steps verify that SlashCommandOverlay.set_commands() renders
command descriptions alongside command names per ADR-046.
"""
from __future__ import annotations
from behave import then, when
from cleveragents.tui.slash_catalog import SLASH_COMMAND_SPECS
@when("I initialise the overlay with real slash command specs")
def step_init_with_real_specs(context):
"""Call set_commands with the real SLASH_COMMAND_SPECS from the catalog."""
context.overlay.set_commands("", list(SLASH_COMMAND_SPECS))
@then("the overlay text should contain description for {command!r}")
def step_overlay_contains_description(context, command):
"""Verify the overlay text contains the description for the given command."""
from cleveragents.tui.slash_catalog import SLASH_COMMAND_SPECS
spec = next((s for s in SLASH_COMMAND_SPECS if s.command == command), None)
assert spec is not None, f"Command {command!r} not found in SLASH_COMMAND_SPECS"
assert spec.description in context.overlay._text, (
f"Expected description '{spec.description}' in:\n{context.overlay._text}"
)
@@ -47,3 +47,11 @@ Feature: TUI Slash Command Overlay Coverage
When I force the fallback static base to load
Then the fallback class should be usable as a standalone object
And calling update on the fallback instance should store the text
Scenario: set_commands renders descriptions alongside command names
Given I have a SlashCommandOverlay instance
When I call set_commands with query "" and commands "session:create,session:list"
Then the overlay text should contain " /session:create"
And the overlay text should contain "Desc for session:create"
And the overlay text should contain " /session:list"
And the overlay text should contain "Desc for session:list"
@@ -0,0 +1,37 @@
Feature: TUI Slash Command Overlay Shows Descriptions
Per ADR-046, the SlashCommandOverlay must display each command with its
description alongside the command name in aligned columns.
Background:
Given the slash command overlay module is imported
Scenario: Overlay renders command name and description in aligned columns
Given I have a SlashCommandOverlay instance
When I call set_commands with query "" and commands "session:create,settings"
Then the overlay text should contain " /session:create"
And the overlay text should contain "Desc for session:create"
And the overlay text should contain " /settings"
And the overlay text should contain "Desc for settings"
Scenario: Overlay renders descriptions for filtered commands
Given I have a SlashCommandOverlay instance
When I call set_commands with query "session" and commands "session:create,session:list,plan:use"
Then the overlay text should contain " /session:create"
And the overlay text should contain "Desc for session:create"
And the overlay text should contain " /session:list"
And the overlay text should contain "Desc for session:list"
And the overlay text should not contain " /plan:use"
Scenario: Overlay uses real slash_command_specs from catalog
Given I have a SlashCommandOverlay instance
When I initialise the overlay with real slash command specs
Then the overlay text should contain " /session:create"
And the overlay text should contain "Create a new session tab"
And the overlay text should contain " /settings"
And the overlay text should contain "Open settings"
@tdd_expected_fail
Scenario: TDD capture - overlay previously showed names only without descriptions
Given I have a SlashCommandOverlay instance
When I initialise the overlay with real slash command specs
Then the overlay text should not contain "Create a new session tab"
+24
View File
@@ -41,6 +41,30 @@ TUI Headless Includes Router Help Payload
Should Contain ${result.stdout} "help"
Should Contain ${result.stdout} /persona
Slash Command Overlay Renders Descriptions
[Documentation] Verify SlashCommandOverlay.set_commands renders descriptions alongside names
${script}= Catenate SEPARATOR=\n
... import importlib
... from unittest.mock import patch
... import cleveragents.tui.widgets.slash_command_overlay as _sco_mod
... from cleveragents.tui.slash_catalog import SLASH_COMMAND_SPECS, slash_command_specs
... with patch("importlib.import_module", side_effect=ImportError("no textual")):
... importlib.reload(_sco_mod)
... overlay = _sco_mod.SlashCommandOverlay()
... overlay.set_commands("", slash_command_specs())
... text = overlay._text
... assert " /session:create" in text, f"Missing /session:create in: {text}"
... assert "Create a new session tab" in text, f"Missing description in: {text}"
... overlay2 = _sco_mod.SlashCommandOverlay()
... overlay2.set_commands("settings", slash_command_specs())
... text2 = overlay2._text
... assert " /settings" in text2, f"Missing /settings in: {text2}"
... assert "Open settings" in text2, f"Missing settings description in: {text2}"
... print("slash-overlay-descriptions-ok")
${result}= Run Process ${PYTHON} -c ${script} shell=False stderr=STDOUT
Should Be Equal As Integers ${result.rc} 0
Should Contain ${result.stdout} slash-overlay-descriptions-ok
TUI Help Panel Context Switching
${script}= Catenate SEPARATOR=\n
... from types import SimpleNamespace
+2 -2
View File
@@ -11,7 +11,7 @@ from cleveragents.tui.first_run import create_default_persona_for_actor, is_firs
from cleveragents.tui.input.modes import InputMode, InputModeRouter
from cleveragents.tui.input.reference_parser import suggestions
from cleveragents.tui.persona.state import PersonaState
from cleveragents.tui.slash_catalog import slash_command_names
from cleveragents.tui.slash_catalog import slash_command_specs
from cleveragents.tui.widgets.actor_selection_overlay import ActorSelectionOverlay
from cleveragents.tui.widgets.help_panel_overlay import (
HelpPanelOverlay,
@@ -130,7 +130,7 @@ if _TEXTUAL_AVAILABLE:
ref_picker = self.query_one("#reference-picker", ReferencePickerOverlay)
ref_picker.set_suggestions("", [])
slash = self.query_one("#slash-overlay", SlashCommandOverlay)
slash.set_commands("", slash_command_names())
slash.set_commands("", slash_command_specs())
actor_overlay = self.query_one("#actor-selection", ActorSelectionOverlay)
if first_run:
actor_overlay.show()
+5
View File
@@ -93,6 +93,11 @@ def slash_command_names() -> list[str]:
return [spec.command for spec in SLASH_COMMAND_SPECS]
def slash_command_specs() -> list[SlashCommandSpec]:
"""Return all slash command specs for the overlay (includes descriptions)."""
return list(SLASH_COMMAND_SPECS)
def slash_command_groups() -> set[str]:
"""Return unique slash command groups represented in the catalog."""
return {spec.group for spec in SLASH_COMMAND_SPECS}
@@ -3,7 +3,10 @@
from __future__ import annotations
import importlib
from typing import Any
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from cleveragents.tui.slash_catalog import SlashCommandSpec
def _load_static_base() -> type[Any]:
@@ -23,17 +26,23 @@ def _load_static_base() -> type[Any]:
_StaticBase = _load_static_base()
_COMMAND_COL_WIDTH = 28 # characters reserved for the command name column
class SlashCommandOverlay(_StaticBase):
"""Renderable list of slash command candidates."""
def set_commands(self, query: str, commands: list[str]) -> None:
def set_commands(self, query: str, commands: list[SlashCommandSpec]) -> None:
filtered = (
[item for item in commands if item.startswith(query)] if query else commands
[item for item in commands if item.command.startswith(query)]
if query
else list(commands)
)
lines = [f"/{query}"]
for command in filtered[:12]:
lines.append(f" /{command}")
for spec in filtered[:12]:
name_col = f" /{spec.command}"
padding = max(1, _COMMAND_COL_WIDTH - len(name_col))
lines.append(f"{name_col}{' ' * padding}{spec.description}")
if len(lines) == 1:
lines.append(" (no commands)")
self.update("\n".join(lines))