Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion openhands_cli/tui/modals/exit_modal.tcss
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ SwitchConversationModal {
padding: 2 0;
}

Button {
#dialog Button {
width: 100%;
height: 3;
margin: 0 1;
Expand Down
4 changes: 4 additions & 0 deletions openhands_cli/tui/widgets/richlog_visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ def _get_agent_model(self) -> str | None:

def on_event(self, event: Event) -> None:
"""Main event handler that creates widgets for events."""
# Dismiss any pending critic feedback widgets when new events arrive,
# since the agent has continued working (user didn't interact with them)
self._run_on_main_thread(self._dismiss_pending_feedback_widgets)

# Check for TaskTrackerObservation to update/open the plan panel
if isinstance(event, ObservationEvent) and isinstance(
event.observation, TaskTrackerObservation
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 46 additions & 1 deletion tests/snapshots/test_critic_feedback_snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from unittest.mock import MagicMock

from textual.app import App, ComposeResult
from textual.containers import Horizontal
from textual.containers import Horizontal, VerticalScroll
from textual.widgets import Button, Footer, Static

from openhands_cli.theme import OPENHANDS_THEME
Expand Down Expand Up @@ -124,3 +124,48 @@ def compose(self) -> ComposeResult:
CriticFeedbackTestApp(),
terminal_size=(100, 20),
)

def test_critic_feedback_widget_inside_vertical_scroll(self, snap_compare):
"""Snapshot test: feedback widget inside VerticalScroll (real app context).

In the real app, CriticFeedbackWidget is mounted inside a VerticalScroll
container. This test reproduces the layout context to verify all 5 buttons
are visible.

Relates to: https://github.com/OpenHands/OpenHands-CLI/issues/641
"""

class CriticFeedbackInScrollApp(App):
CSS = """
Screen {
background: $background;
}
#scroll_view {
width: 100%;
height: 1fr;
}
#content {
width: 100%;
height: auto;
padding: 1;
}
"""

def __init__(self, **kwargs):
super().__init__(**kwargs)
self.register_theme(OPENHANDS_THEME)
self.theme = OPENHANDS_THEME.name

def compose(self) -> ComposeResult:
with VerticalScroll(id="scroll_view"):
yield Static(
"Sample conversation content above the widget",
id="content",
)
yield MockCriticFeedbackWidget()
yield Footer()

assert snap_compare(
CriticFeedbackInScrollApp(),
terminal_size=(100, 20),
)
42 changes: 42 additions & 0 deletions tests/tui/modals/test_exit_modal.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,54 @@
"""Tests for exit confirmation modal functionality."""

import re
from pathlib import Path
from unittest import mock

from textual.widgets import Button

from openhands_cli.tui.modals.exit_modal import ExitConfirmationModal


# Path to exit modal TCSS relative to the modal module
EXIT_MODAL_TCSS_PATH = (
Path(__file__).resolve().parents[3]
/ "openhands_cli"
/ "tui"
/ "modals"
/ "exit_modal.tcss"
)


class TestExitModalCssScoping:
"""Tests that exit modal CSS doesn't leak global styles.

Regression test for GitHub issue #641: an unscoped `Button { width: 100%; }`
rule in exit_modal.tcss was overriding CriticFeedbackWidget button styles,
causing only the first button to be visible.
"""

def test_button_rule_is_scoped_to_dialog(self):
"""Button CSS rules must be scoped (e.g., #dialog Button), not global.

An unscoped `Button { ... }` rule would override button styles
in every other widget (e.g., CriticFeedbackWidget).
"""
tcss_content = EXIT_MODAL_TCSS_PATH.read_text()

# Find all Button selector lines (ignoring comments)
# Match lines like "Button {" but not "#dialog Button {" or ".foo Button {"
unscoped_button_rules = re.findall(
r"^\s*Button\s*\{", tcss_content, re.MULTILINE
)

assert not unscoped_button_rules, (
"Found unscoped global 'Button' rule(s) in exit_modal.tcss. "
"Button rules must be scoped to a parent (e.g., '#dialog Button') "
"to avoid overriding button styles in other widgets. "
"See: https://github.com/OpenHands/OpenHands-CLI/issues/641"
)


class TestExitConfirmationModal:
"""Tests for the ExitConfirmationModal component."""

Expand Down
Loading
Loading