From 616a97c40a12a606ccea107052e294ac7dbf2e8d Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 13 Nov 2026 00:00:04 -0500 Subject: [PATCH 1/3] Dock command buttons below the scrolling form --- src/toad/widgets/comms_command_dialog.py | 18 ++++++++++++++---- ...t_application_actions_in_the_running_app.py | 14 +++++++++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/toad/widgets/comms_command_dialog.py b/src/toad/widgets/comms_command_dialog.py index 6ae20912d..71718188c 100644 --- a/src/toad/widgets/comms_command_dialog.py +++ b/src/toad/widgets/comms_command_dialog.py @@ -30,7 +30,13 @@ from textual.worker import Worker, WorkerFailed from toad.forms.editors import MultilineEditor, ValueEditor from toad.forms.form_view import InlineFormView -from toad.native_style import AutoWidth, EllipsisOverflow, SideMargin +from toad.native_style import ( + AutoWidth, + DockedBottom, + EllipsisOverflow, + FillHeight, + SideMargin, +) from toad.screens.modal import ( CenteredScreen, DismissibleScreen, @@ -168,13 +174,17 @@ def apply_native_layout(self) -> None: self.styles.padding = (1, 2) +class CommandFormScroll(FillHeight, VerticalScroll): + """The form scrolls within the space left above its fixed command footer.""" + + class CommandTitle(EllipsisOverflow, Static): def apply_native_layout(self) -> None: super().apply_native_layout() self.styles.max_height = 3 -class CommandButtons(Horizontal): +class CommandButtons(DockedBottom, Horizontal): def apply_native_layout(self) -> None: super().apply_native_layout() self.styles.height = 3 @@ -231,10 +241,10 @@ def __init__(self, definition: TargetAction, target: str, fields: tuple[TargetFi def compose(self) -> ComposeResult: with CommandBox(): yield CommandTitle(f"{self.definition.label} — {self.target}", markup=False) - with VerticalScroll(): + with CommandFormScroll(): yield CommandConfirmation('', markup=False) yield self.form - yield CommandConfirmed('I confirm this operation') + yield CommandConfirmed('I confirm this operation') with CommandButtons(): yield ApplyCommandButton('Apply', variant='primary') yield CancelCommandButton('Cancel') diff --git a/tests/textual/test_application_actions_in_the_running_app.py b/tests/textual/test_application_actions_in_the_running_app.py index 8e0b9c638..46f43048f 100644 --- a/tests/textual/test_application_actions_in_the_running_app.py +++ b/tests/textual/test_application_actions_in_the_running_app.py @@ -16,7 +16,11 @@ from toad.preferences import SETTINGS from toad.screens.keymap import KeymapScreen from toad.screens.settings import SettingsScreen -from toad.widgets.comms_command_dialog import CatalogField, CatalogFieldSelection +from toad.widgets.comms_command_dialog import ( + CatalogField, + CatalogFieldSelection, + CommandFormScroll, +) from toad.widgets.comms_sidebar import CommsSidebar, NewSessionButton from toad.widgets.footer import ToadFooter from toad.widgets.session_tabs import SessionTabClose @@ -180,8 +184,16 @@ def call(self, request): parameter = dialog.choices['model'].parameter assert selection.current == FieldCodec.encode(parameter.value) assert selection.choices == tuple(choice for choice in parameter.options if choice.value is not None) + await ui.textual[1].resize_terminal(80, 24) + await ui.step() button = dialog.query_one(ApplyCommandButton) await ui.until(lambda: not button.disabled, why="the command values to be accepted") + assert button.region.area and button.visible_region == button.region + service = dialog.form.values['service_tier'].editor + service.scroll_visible(animate=False, immediate=True) + await ui.until_clickable(service, why="the lower service-level field to scroll into view") + assert dialog.query_one(CommandFormScroll).scroll_y > 0 + assert button.visible_region == button.region editor = dialog.form.values["name"].editor editor.scroll_visible(animate=False, immediate=True) await ui.until_clickable(editor, why="the name editor to be visible") From 96a36ca5eb7898c5b9cc007e95b3ddef46d8c51b Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 13 Nov 2026 00:08:23 -0500 Subject: [PATCH 2/3] Check the scrolled selector through its actual click --- .../test_application_actions_in_the_running_app.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/textual/test_application_actions_in_the_running_app.py b/tests/textual/test_application_actions_in_the_running_app.py index 46f43048f..2c98b77bc 100644 --- a/tests/textual/test_application_actions_in_the_running_app.py +++ b/tests/textual/test_application_actions_in_the_running_app.py @@ -188,12 +188,15 @@ def call(self, request): await ui.step() button = dialog.query_one(ApplyCommandButton) await ui.until(lambda: not button.disabled, why="the command values to be accepted") - assert button.region.area and button.visible_region == button.region - service = dialog.form.values['service_tier'].editor - service.scroll_visible(animate=False, immediate=True) - await ui.until_clickable(service, why="the lower service-level field to scroll into view") + await ui.until_clickable(button, why="the fixed create control to remain visible before scrolling") + lower = next(reversed(dialog.form.values.values())).editor + lower.scroll_visible(animate=False, immediate=True) + assert isinstance(lower, ChoicesEditor) + assert await ui.textual[1].click(lower, offset=(3, 1)) + await ui.until(lambda: lower.expanded, why="the scrolled lower field to open its actual choices") + await ui.press('escape') assert dialog.query_one(CommandFormScroll).scroll_y > 0 - assert button.visible_region == button.region + await ui.until_clickable(button, why="the fixed create control to remain visible after scrolling") editor = dialog.form.values["name"].editor editor.scroll_visible(animate=False, immediate=True) await ui.until_clickable(editor, why="the name editor to be visible") From fde6178cb446208649abc0677f7d8c2600f79675 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 13 Nov 2026 00:10:57 -0500 Subject: [PATCH 3/3] Assert the opened select instead of its parent hit identity --- tests/textual/test_application_actions_in_the_running_app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/textual/test_application_actions_in_the_running_app.py b/tests/textual/test_application_actions_in_the_running_app.py index 2c98b77bc..bacfbba45 100644 --- a/tests/textual/test_application_actions_in_the_running_app.py +++ b/tests/textual/test_application_actions_in_the_running_app.py @@ -192,7 +192,8 @@ def call(self, request): lower = next(reversed(dialog.form.values.values())).editor lower.scroll_visible(animate=False, immediate=True) assert isinstance(lower, ChoicesEditor) - assert await ui.textual[1].click(lower, offset=(3, 1)) + await ui.step() + await ui.textual[1].click(lower, offset=(3, 1)) await ui.until(lambda: lower.expanded, why="the scrolled lower field to open its actual choices") await ui.press('escape') assert dialog.query_one(CommandFormScroll).scroll_y > 0