Repository navigation
fix(tui): suppress mouse-residue leaks during Python launcher startup #31213
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
1f5be35
fix(tui): suppress mouse-residue leaks during Python launcher startup
OutThisLife a2dd80d
test(tui): drop dead _reload_main, hoist import out of patch context
OutThisLife f58be6c
fix(tui): skip early mouse-disable when stdout is not a TTY
OutThisLife f938bbd
docs(tui): rephrase 'raw cooked mode' as 'cooked + echo mode'
OutThisLife File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| """Tests for the TUI-hot-path mouse-residue suppression. | ||
|
|
||
| The Python launcher (`hermes --tui …`) has a ~100–300ms cold-start window | ||
| where stdin is still in cooked + echo mode. If a previous Hermes session | ||
| left DEC mouse-tracking asserted, any mouse motion during that window | ||
| echoes literal ``^[[<…M`` text into the user's scrollback. | ||
|
|
||
| `_suppress_mouse_residue_early()` writes the disable sequence to stdout | ||
| before the heavy imports so the terminal stops emitting events ASAP. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import sys | ||
| from unittest.mock import patch | ||
|
|
||
| # Importing the module triggers `_suppress_mouse_residue_early()` at module | ||
| # scope. Under the test runner argv (`pytest …`) it's a no-op, but we import | ||
| # at file scope so individual tests don't race the import side-effect with | ||
| # their `patch("os.write")` context. | ||
| from hermes_cli.main import _suppress_mouse_residue_early | ||
|
|
||
| EXPECTED = ( | ||
| b"\x1b[?1003l\x1b[?1002l\x1b[?1001l\x1b[?1000l\x1b[?9l" | ||
| b"\x1b[?1006l\x1b[?1005l\x1b[?1015l\x1b[?1016l\x1b[?2029l" | ||
| ) | ||
|
|
||
|
|
||
| class TestEarlyMouseDisable: | ||
| def test_writes_disable_sequence_when_tui_flag_in_argv(self, monkeypatch): | ||
| monkeypatch.setattr(sys, "argv", ["hermes", "--tui", "-c", "abc"]) | ||
| monkeypatch.delenv("HERMES_TUI", raising=False) | ||
| monkeypatch.delenv("HERMES_TUI_NO_EARLY_DISABLE", raising=False) | ||
|
|
||
| with patch("os.isatty", return_value=True), patch("os.write") as mock_write: | ||
| _suppress_mouse_residue_early() | ||
|
|
||
| mock_write.assert_called_once_with(1, EXPECTED) | ||
|
|
||
| def test_writes_disable_sequence_when_hermes_tui_env_set(self, monkeypatch): | ||
| monkeypatch.setattr(sys, "argv", ["hermes"]) | ||
| monkeypatch.setenv("HERMES_TUI", "1") | ||
| monkeypatch.delenv("HERMES_TUI_NO_EARLY_DISABLE", raising=False) | ||
|
|
||
| with patch("os.isatty", return_value=True), patch("os.write") as mock_write: | ||
| _suppress_mouse_residue_early() | ||
|
|
||
| mock_write.assert_called_once_with(1, EXPECTED) | ||
|
|
||
| def test_no_op_on_non_tui_invocation(self, monkeypatch): | ||
| monkeypatch.setattr(sys, "argv", ["hermes", "--version"]) | ||
| monkeypatch.delenv("HERMES_TUI", raising=False) | ||
| monkeypatch.delenv("HERMES_TUI_NO_EARLY_DISABLE", raising=False) | ||
|
|
||
| with patch("os.write") as mock_write: | ||
| _suppress_mouse_residue_early() | ||
|
|
||
| mock_write.assert_not_called() | ||
|
|
||
| def test_respects_diagnostic_escape_hatch(self, monkeypatch): | ||
| monkeypatch.setattr(sys, "argv", ["hermes", "--tui"]) | ||
| monkeypatch.delenv("HERMES_TUI", raising=False) | ||
| monkeypatch.setenv("HERMES_TUI_NO_EARLY_DISABLE", "1") | ||
|
|
||
| with patch("os.write") as mock_write: | ||
| _suppress_mouse_residue_early() | ||
|
|
||
| mock_write.assert_not_called() | ||
|
|
||
| def test_skips_when_stdout_is_not_a_tty(self, monkeypatch): | ||
| # `hermes --tui … >log` or CI capture: pipe is fd 1, not a TTY. The | ||
| # bytes can't reach a terminal and would just pollute the log. | ||
| monkeypatch.setattr(sys, "argv", ["hermes", "--tui"]) | ||
| monkeypatch.delenv("HERMES_TUI", raising=False) | ||
| monkeypatch.delenv("HERMES_TUI_NO_EARLY_DISABLE", raising=False) | ||
|
|
||
| with patch("os.isatty", return_value=False), patch("os.write") as mock_write: | ||
| _suppress_mouse_residue_early() | ||
|
|
||
| mock_write.assert_not_called() | ||
|
|
||
| def test_oserror_is_swallowed(self, monkeypatch): | ||
| monkeypatch.setattr(sys, "argv", ["hermes", "--tui"]) | ||
| monkeypatch.delenv("HERMES_TUI", raising=False) | ||
| monkeypatch.delenv("HERMES_TUI_NO_EARLY_DISABLE", raising=False) | ||
|
|
||
| def boom(*_a, **_k): | ||
| raise OSError("stdout closed") | ||
|
|
||
| with patch("os.isatty", return_value=True), patch("os.write", side_effect=boom): | ||
| # Must not propagate — startup hot path can never break. | ||
| _suppress_mouse_residue_early() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.