Skip to content

Commit 7bb0477

Browse files
Add /feedback command for users to send anonymous feedback about CLI (#293)
* Add /feedback command to open anonymous feedback form This adds a new /feedback command that opens a Google Form in the user's browser where they can submit anonymous feedback about the CLI. Changes: - Add /feedback to COMMANDS list in commands.py - Add /feedback to show_help function - Implement _handle_feedback_command in textual_app.py - Add tests for the new command Closes #241 Co-authored-by: openhands <openhands@all-hands.dev> * Add /feedback to splash page and add test for commands in help - Update splash page instructions to mention /feedback for anonymous feedback - Add test to verify all commands from COMMANDS list are included in help text - Add test to verify /help and /feedback are mentioned in splash instructions Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev>
1 parent 8e550e0 commit 7bb0477

File tree

5 files changed

+93
-3
lines changed

5 files changed

+93
-3
lines changed

openhands_cli/tui/content/splash.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ def get_splash_content(conversation_id: str, *, theme: Theme) -> dict:
5959
"1. Ask questions, edit files, or run commands.",
6060
"2. Use @ to look up a file in the folder structure",
6161
(
62-
"3. Type /help for help or / to immediately scroll through "
63-
"available commands"
62+
"3. Type /help for help, /feedback to leave anonymous feedback, "
63+
"or / to scroll through available commands"
6464
),
6565
],
6666
"update_notice": None,

openhands_cli/tui/core/commands.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
DropdownItem(main="/help - Display available commands"),
1717
DropdownItem(main="/confirm - Configure confirmation settings"),
1818
DropdownItem(main="/condense - Condense conversation history"),
19+
DropdownItem(main="/feedback - Send anonymous feedback about CLI"),
1920
DropdownItem(main="/exit - Exit the application"),
2021
]
2122

@@ -66,6 +67,7 @@ def show_help(main_display: VerticalScroll) -> None:
6667
[{secondary}]/help[/{secondary}] - Display available commands
6768
[{secondary}]/confirm[/{secondary}] - Configure confirmation settings
6869
[{secondary}]/condense[/{secondary}] - Condense conversation history
70+
[{secondary}]/feedback[/{secondary}] - Send anonymous feedback about CLI
6971
[{secondary}]/exit[/{secondary}] - Exit the application
7072
7173
[dim]Tips:[/dim]

openhands_cli/tui/textual_app.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,8 @@ def _handle_command(self, command: str) -> None:
405405
self._handle_confirm_command()
406406
elif command == "/condense":
407407
self._handle_condense_command()
408+
elif command == "/feedback":
409+
self._handle_feedback_command()
408410
elif command == "/exit":
409411
self._handle_exit()
410412
else:
@@ -560,6 +562,18 @@ def _handle_condense_command(self) -> None:
560562
# This will handle all error cases and notifications
561563
asyncio.create_task(self.conversation_runner.condense_async())
562564

565+
def _handle_feedback_command(self) -> None:
566+
"""Handle the /feedback command to open feedback form in browser."""
567+
import webbrowser
568+
569+
feedback_url = "https://forms.gle/chHc5VdS3wty5DwW6"
570+
webbrowser.open(feedback_url)
571+
self.notify(
572+
title="Feedback",
573+
message="Opening feedback form in your browser...",
574+
severity="information",
575+
)
576+
563577
def _handle_confirmation_request(
564578
self, pending_actions: list[ActionEvent]
565579
) -> UserConfirmation:

tests/tui/test_commands.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class TestCommands:
2323
def test_commands_list_structure(self):
2424
"""Test that COMMANDS list has correct structure."""
2525
assert isinstance(COMMANDS, list)
26-
assert len(COMMANDS) == 4
26+
assert len(COMMANDS) == 5
2727

2828
# Check that all items are DropdownItems
2929
for command in COMMANDS:
@@ -38,6 +38,7 @@ def test_commands_list_structure(self):
3838
("/help", "Display available commands"),
3939
("/confirm", "Configure confirmation settings"),
4040
("/condense", "Condense conversation history"),
41+
("/feedback", "Send anonymous feedback about CLI"),
4142
("/exit", "Exit the application"),
4243
],
4344
)
@@ -73,10 +74,12 @@ def test_show_help_function_signature(self):
7374
"/help",
7475
"/confirm",
7576
"/condense",
77+
"/feedback",
7678
"/exit",
7779
"Display available commands",
7880
"Configure confirmation settings",
7981
"Condense conversation history",
82+
"Send anonymous feedback about CLI",
8083
"Exit the application",
8184
"Tips:",
8285
"Type / and press Tab",
@@ -144,6 +147,7 @@ def test_show_help_formatting(self):
144147
("/help", True),
145148
("/confirm", True),
146149
("/condense", True),
150+
("/feedback", True),
147151
("/exit", True),
148152
("/help extra", False),
149153
("/exit now", False),
@@ -157,6 +161,36 @@ def test_is_valid_command(self, cmd, expected):
157161
"""Command validation is strict and argument-sensitive."""
158162
assert is_valid_command(cmd) is expected
159163

164+
def test_all_commands_included_in_help(self):
165+
"""Test that all commands from COMMANDS list are included in help text.
166+
167+
This ensures that when new commands are added to COMMANDS, they are also
168+
added to the help text displayed by show_help().
169+
"""
170+
from openhands_cli.tui.core.commands import get_valid_commands
171+
172+
mock_main_display = mock.MagicMock(spec=VerticalScroll)
173+
show_help(mock_main_display)
174+
175+
# Get the help text that was mounted
176+
mock_main_display.mount.assert_called_once()
177+
help_widget = mock_main_display.mount.call_args[0][0]
178+
help_text = help_widget.content
179+
180+
# Get all valid commands from COMMANDS list
181+
valid_commands = get_valid_commands()
182+
183+
# Verify each command is present in the help text
184+
missing_commands = []
185+
for command in valid_commands:
186+
if command not in help_text:
187+
missing_commands.append(command)
188+
189+
assert not missing_commands, (
190+
f"The following commands are defined in COMMANDS but missing from "
191+
f"help text: {missing_commands}"
192+
)
193+
160194

161195
class TestOpenHandsAppCommands:
162196
"""Integration-style tests for command handling in OpenHandsApp."""
@@ -355,3 +389,40 @@ async def test_condense_command_no_runner_error_message(
355389
message="No conversation available to condense",
356390
severity="error",
357391
)
392+
393+
@pytest.mark.asyncio
394+
async def test_feedback_command_opens_browser(
395+
self,
396+
monkeypatch: pytest.MonkeyPatch,
397+
) -> None:
398+
"""`/feedback` should open the feedback form URL in the browser."""
399+
monkeypatch.setattr(
400+
SettingsScreen,
401+
"is_initial_setup_required",
402+
lambda: False,
403+
)
404+
405+
app = OpenHandsApp(exit_confirmation=False)
406+
407+
async with app.run_test() as pilot:
408+
oh_app = cast(OpenHandsApp, pilot.app)
409+
410+
# Mock webbrowser.open to verify it's called with correct URL
411+
with mock.patch("webbrowser.open") as mock_browser:
412+
# Mock notify to verify notification is shown
413+
notify_mock = mock.MagicMock()
414+
oh_app.notify = notify_mock
415+
416+
oh_app._handle_command("/feedback")
417+
418+
# Verify browser was opened with correct URL
419+
mock_browser.assert_called_once_with(
420+
"https://forms.gle/chHc5VdS3wty5DwW6"
421+
)
422+
423+
# Verify notification was shown
424+
notify_mock.assert_called_once_with(
425+
title="Feedback",
426+
message="Opening feedback form in your browser...",
427+
severity="information",
428+
)

tests/tui/test_splash.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ def test_splash_content_with_conversation_id(self):
6666
"2. Use @ to look up a file in the folder structure"
6767
in content["instructions"][1]
6868
)
69+
# Verify /help and /feedback are mentioned in instructions
70+
assert "/help" in content["instructions"][2]
71+
assert "/feedback" in content["instructions"][2]
6972

7073
# Should contain conversation ID
7174
assert "conversation_text" in content

0 commit comments

Comments
 (0)