From 434730bb7e44675d1b1434092e574496c71d47df Mon Sep 17 00:00:00 2001 From: Mason Daugherty <61371264+mdrxy@users.noreply.github.com> Date: Wed, 12 Aug 2026 04:44:34 +0000 Subject: [PATCH 1/2] fix(code): indent wrapped ask-user choices Co-authored-by: open-swe[bot] --- .../tui/widgets/_inline_prompt.py | 50 ++++++++++++++++--- .../unit_tests/tui/widgets/test_ask_user.py | 8 ++- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/libs/code/deepagents_code/tui/widgets/_inline_prompt.py b/libs/code/deepagents_code/tui/widgets/_inline_prompt.py index 904a14457b7..29f771700ee 100644 --- a/libs/code/deepagents_code/tui/widgets/_inline_prompt.py +++ b/libs/code/deepagents_code/tui/widgets/_inline_prompt.py @@ -8,6 +8,7 @@ from collections import Counter from typing import TYPE_CHECKING, Any, Generic, TypeVar +from textual.containers import Horizontal from textual.content import Content from textual.message import Message from textual.widgets import Static @@ -16,6 +17,7 @@ from pathlib import Path from textual import events + from textual.app import ComposeResult from textual.widget import Widget from deepagents_code import theme @@ -294,8 +296,24 @@ async def _reject_dropped_media(self, text: str) -> bool: return True -class InlinePromptOption(Static): - """Render a selectable inline-prompt option with a cursor.""" +class InlinePromptOption(Horizontal): + """Render a selectable inline-prompt option with a cursor gutter.""" + + DEFAULT_CSS = """ + InlinePromptOption { + height: auto; + } + + InlinePromptOption > .inline-prompt-option-cursor { + width: 2; + height: 1; + } + + InlinePromptOption > .inline-prompt-option-label { + width: 1fr; + height: auto; + } + """ def __init__( self, @@ -313,16 +331,33 @@ def __init__( index: Position in its owning prompt's option list. selected: Whether to render the option selected initially. selected_class: CSS class applied while the option is highlighted. - **kwargs: Additional `Static` arguments. + **kwargs: Additional `Horizontal` arguments. """ self.option_index = index self._cursor_visible = selected self._highlighted = selected self._text = text self._selected_class = selected_class - super().__init__(self._render(), **kwargs) + self._cursor_widget: Static | None = None + super().__init__(**kwargs) self._sync_selected_class() + def compose(self) -> ComposeResult: + """Compose the cursor gutter and independently wrapping label. + + Yields: + The fixed cursor gutter followed by the wrapping label. + """ + self._cursor_widget = Static( + self._cursor_content(), + classes="inline-prompt-option-cursor", + ) + yield self._cursor_widget + yield Static( + Content.from_markup("$text", text=self._text), + classes="inline-prompt-option-label", + ) + @property def selected(self) -> bool: """Whether the selection cursor is currently shown on this option.""" @@ -345,13 +380,14 @@ def set_state(self, *, cursor: bool, highlighted: bool) -> None: """ self._cursor_visible = cursor self._highlighted = highlighted - self.update(self._render()) + if self._cursor_widget is not None: + self._cursor_widget.update(self._cursor_content()) self._sync_selected_class() - def _render(self) -> Content: + def _cursor_content(self) -> Content: glyphs = get_glyphs() prefix = f"{glyphs.cursor} " if self._cursor_visible else " " - return Content.from_markup("$prefix$text", prefix=prefix, text=self._text) + return Content(prefix) def _sync_selected_class(self) -> None: if self._selected_class is None: diff --git a/libs/code/tests/unit_tests/tui/widgets/test_ask_user.py b/libs/code/tests/unit_tests/tui/widgets/test_ask_user.py index c1c44a2e351..6fda0d9d3c3 100644 --- a/libs/code/tests/unit_tests/tui/widgets/test_ask_user.py +++ b/libs/code/tests/unit_tests/tui/widgets/test_ask_user.py @@ -18,6 +18,7 @@ MISSING_ANSWER_TOAST, AskUserMenu, AskUserTextArea, + _ChoiceOption, _QuestionWidget, ) @@ -210,8 +211,11 @@ async def test_multiple_choice_option_wraps_in_narrow_menu(self) -> None: async with app.run_test(size=(36, 24)) as pilot: await pilot.pause() - choice = app.query_one(".ask-user-choice", Static) - assert choice.size.height > 1 + choice = app.query_one(".ask-user-choice", _ChoiceOption) + cursor = choice.query_one(".inline-prompt-option-cursor", Static) + label = choice.query_one(".inline-prompt-option-label", Static) + assert label.size.height > 1 + assert label.region.x == cursor.region.x + cursor.region.width async def test_text_question_submits_typed_answer(self) -> None: app = _AskUserTestApp([{"question": "What is your name?", "type": "text"}]) From 9f8a019f6760efd7e00d1c9bc2bf57af3a4828c2 Mon Sep 17 00:00:00 2001 From: Mason Daugherty Date: Tue, 11 Aug 2026 22:31:37 -0700 Subject: [PATCH 2/2] feat(code): highlight selected ask-user choice and add bullet markers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Selected choice labels now render in the theme primary color, and every option gets a bullet (`•`) in the cursor gutter so the start of each option is visually distinct from wrapped continuation lines. The `_unselected_marker` property on `InlinePromptOption` defaults to a blank space (preserving goal-review behavior); `_ChoiceOption` overrides it to show the bullet glyph. --- .../deepagents_code/tui/widgets/_inline_prompt.py | 15 ++++++++++++--- libs/code/deepagents_code/tui/widgets/ask_user.py | 4 ++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/libs/code/deepagents_code/tui/widgets/_inline_prompt.py b/libs/code/deepagents_code/tui/widgets/_inline_prompt.py index 29f771700ee..9e89b798635 100644 --- a/libs/code/deepagents_code/tui/widgets/_inline_prompt.py +++ b/libs/code/deepagents_code/tui/widgets/_inline_prompt.py @@ -313,6 +313,10 @@ class InlinePromptOption(Horizontal): width: 1fr; height: auto; } + + InlinePromptOption.inline-prompt-option-selected > .inline-prompt-option-label { + color: $primary; + } """ def __init__( @@ -321,7 +325,7 @@ def __init__( index: int, *, selected: bool = False, - selected_class: str | None = None, + selected_class: str | None = "inline-prompt-option-selected", **kwargs: Any, ) -> None: """Initialize an option. @@ -386,8 +390,13 @@ def set_state(self, *, cursor: bool, highlighted: bool) -> None: def _cursor_content(self) -> Content: glyphs = get_glyphs() - prefix = f"{glyphs.cursor} " if self._cursor_visible else " " - return Content(prefix) + marker = glyphs.cursor if self._cursor_visible else self._unselected_marker + return Content(f"{marker} ") + + @property + def _unselected_marker(self) -> str: + """Marker shown in the cursor gutter when this option is not selected.""" + return " " def _sync_selected_class(self) -> None: if self._selected_class is None: diff --git a/libs/code/deepagents_code/tui/widgets/ask_user.py b/libs/code/deepagents_code/tui/widgets/ask_user.py index 1c3290dd73f..67e8bcc7308 100644 --- a/libs/code/deepagents_code/tui/widgets/ask_user.py +++ b/libs/code/deepagents_code/tui/widgets/ask_user.py @@ -356,6 +356,10 @@ def on_blur(self, event: events.Blur) -> None: # noqa: PLR6301 # Textual event class _ChoiceOption(InlinePromptOption): """A single selectable ask-user choice option.""" + @property + def _unselected_marker(self) -> str: + return get_glyphs().bullet + def __init__( self, text: str, index: int, *, selected: bool = False, **kwargs: Any ) -> None: