diff --git a/libs/code/deepagents_code/widgets/welcome.py b/libs/code/deepagents_code/widgets/welcome.py index ac77ba0f1e3..44178bcea13 100644 --- a/libs/code/deepagents_code/widgets/welcome.py +++ b/libs/code/deepagents_code/widgets/welcome.py @@ -13,7 +13,7 @@ from textual.widgets import Static if TYPE_CHECKING: - from textual.events import Click + from textual.events import Click, MouseMove from textual.timer import Timer from deepagents_code import theme @@ -341,6 +341,18 @@ def on_click(self, event: Click) -> None: # noqa: PLR6301 # Textual event hand """Open style-embedded hyperlinks on single click.""" open_style_link(event) + def on_mouse_move(self, event: MouseMove) -> None: + """Show a hand pointer over link spans and reset it elsewhere. + + `auto_links` is disabled to avoid a hover-refresh flicker, so the + pointer shape is updated manually from the style under the cursor. + """ + self.styles.pointer = "pointer" if event.style.link else "default" + + def on_leave(self) -> None: + """Reset the pointer shape when the mouse leaves the banner.""" + self.styles.pointer = "default" + def _build_banner(self, project_url: str | None = None) -> Content: """Build the banner content. diff --git a/libs/code/tests/unit_tests/test_welcome.py b/libs/code/tests/unit_tests/test_welcome.py index 6477a74ae11..94b5be70e6b 100644 --- a/libs/code/tests/unit_tests/test_welcome.py +++ b/libs/code/tests/unit_tests/test_welcome.py @@ -366,6 +366,40 @@ def test_click_with_browser_error_is_graceful(self) -> None: event.stop.assert_not_called() +class TestPointerShapeOnHover: + """Tests for the hand pointer shown when hovering link spans.""" + + def test_mouse_move_over_link_sets_pointer(self) -> None: + """Hovering a link span should show the hand pointer.""" + widget = _make_banner(thread_id="abc") + event = MagicMock() + event.style = Style(link="https://example.com") + + widget.on_mouse_move(event) + + assert widget.styles.pointer == "pointer" + + def test_mouse_move_off_link_resets_pointer(self) -> None: + """Hovering non-link text should reset to the default pointer.""" + widget = _make_banner(thread_id="abc") + widget.styles.pointer = "pointer" + event = MagicMock() + event.style = Style() + + widget.on_mouse_move(event) + + assert widget.styles.pointer == "default" + + def test_leave_resets_pointer(self) -> None: + """Leaving the banner should reset to the default pointer.""" + widget = _make_banner(thread_id="abc") + widget.styles.pointer = "pointer" + + widget.on_leave() + + assert widget.styles.pointer == "default" + + class TestBuildWelcomeFooter: """Tests for the `build_welcome_footer` standalone function."""