From fd5fe5b32838d7f148ca7cc0709d9d09edad6e3b Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Mon, 1 Jun 2026 03:04:47 -0400 Subject: [PATCH] More progressbar updates. Removed Claud specific progress bar detection, hopefully caught now by generic progress bar updates. They do change it all the time, so it may work, but shouldn't be expected to do so. --- README.md | 4 +++ docs/user.md | 3 ++ .../onScreenUpdate/65000-progress_detector.py | 36 ++++++------------- src/fenrirscreenreader/fenrirVersion.py | 2 +- tests/unit/test_progress_detector.py | 31 ++++++++++++++++ 5 files changed, 49 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 2a07be1f..fbb75339 100644 --- a/README.md +++ b/README.md @@ -653,6 +653,10 @@ Building... - **Non-blocking**: Progress tones don't interrupt speech or other functionality - **Configurable**: Can be enabled/disabled as needed +Fenrir detects stable progress structures rather than application-specific +status formats. Application-specific formats change too frequently to support +reliably. + ### Usage Examples ```bash diff --git a/docs/user.md b/docs/user.md index 9f70fcab..bece798e 100644 --- a/docs/user.md +++ b/docs/user.md @@ -312,6 +312,9 @@ Fenrir automatically detects and provides audio feedback for progress indicators - **Automatic**: Works with downloads, compilations, installations - **Remote control**: Enable via socket commands +Fenrir detects stable progress structures rather than application-specific +status formats, which change too frequently to support reliably. + ### Spell Checking - `Fenrir + S` - Spell check current word - `Fenrir + S S` - Add word to dictionary diff --git a/src/fenrirscreenreader/commands/onScreenUpdate/65000-progress_detector.py b/src/fenrirscreenreader/commands/onScreenUpdate/65000-progress_detector.py index f97270fc..6374ad15 100644 --- a/src/fenrirscreenreader/commands/onScreenUpdate/65000-progress_detector.py +++ b/src/fenrirscreenreader/commands/onScreenUpdate/65000-progress_detector.py @@ -81,7 +81,7 @@ class command: delta_length = len(delta_text) if ( delta_length > 200 - ): # Allow longer progress lines like Claude Code's status + ): # Allow longer progress lines such as terminal status output if not self.is_explicit_progress_delta(delta_text): self.env["runtime"]["DebugManager"].write_debug_out( f"Progress filter: delta too long ({delta_length})", @@ -326,43 +326,27 @@ class command: self.env["commandBuffer"]["lastProgressTime"] = current_time return - # Pattern 6: Claude Code working indicators (various symbols + activity text + "esc/ctrl+c to interrupt") - # Matches any: [symbol] [Task description]… (... to interrupt ...) + # Pattern 6: Interruptible terminal activity indicators + # Matches any: [symbol] [Task description][…] (... to interrupt ...) # Symbols include: * ✢ ✽ ✶ ✻ · • ◦ ○ ● ◆ and similar decorative characters - # Example: ✽ Reviewing script for issues… (ctrl+c to interrupt · 33s · ↑ 1.6k tokens · thought for 4s) - claude_progress_match = re.search( - r'[*✢✽✶✻·•◦○●◆]\s+\w+.*?…\s*\(.*(?:esc|ctrl\+c) to interrupt.*\)', + # Keep this structural rather than adding application-specific formats, + # which change too frequently to support reliably. + interruptible_activity_match = re.search( + r'[*✢✽✶✻·•◦○●◆]\s+\w+.*?(?:…\s*)?\(.*(?:esc|ctrl\+c) to interrupt.*\)', text, re.IGNORECASE, ) - if claude_progress_match: + if interruptible_activity_match: if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0: self.env["runtime"]["DebugManager"].write_debug_out( - "Playing Claude Code activity beep", + "Playing interruptible activity beep", debug.DebugLevel.INFO, ) self.play_activity_beep() self.env["commandBuffer"]["lastProgressTime"] = current_time return - # Pattern 6b: Claude Code tool invocation indicators (● Tool Name(...)) - # Example: ● Web Search("query here") - tool_invocation_match = re.search( - r'[●○◉•◦]\s+(?:Web\s*Search|Read|Write|Edit|Bash|Glob|Grep|Task|WebFetch)\s*\(', - text, - re.IGNORECASE, - ) - if tool_invocation_match: - if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0: - self.env["runtime"]["DebugManager"].write_debug_out( - "Playing Claude Code tool invocation beep", - debug.DebugLevel.INFO, - ) - self.play_activity_beep() - self.env["commandBuffer"]["lastProgressTime"] = current_time - return - - # Pattern 6c: Bullet/white bullet activity lines (•/◦ ...) + # Pattern 6b: Bullet/white bullet activity lines (•/◦ ...) bullet_activity_match = re.search( ( r'^\s*[•◦]\s+.*(?:…|\.{3,}|\b(?:thinking|working|processing|' diff --git a/src/fenrirscreenreader/fenrirVersion.py b/src/fenrirscreenreader/fenrirVersion.py index 9bd83cd7..3f4944d9 100644 --- a/src/fenrirscreenreader/fenrirVersion.py +++ b/src/fenrirscreenreader/fenrirVersion.py @@ -4,5 +4,5 @@ # Fenrir TTY screen reader # By Chrys, Storm Dragon, and contributors. -version = "2026.05.31" +version = "2026.06.01" code_name = "master" diff --git a/tests/unit/test_progress_detector.py b/tests/unit/test_progress_detector.py index 0a77d57d..d0ec90be 100644 --- a/tests/unit/test_progress_detector.py +++ b/tests/unit/test_progress_detector.py @@ -109,3 +109,34 @@ def test_progress_detector_beeps_for_long_tqdm_transfer_delta(): command.play_progress_tone.assert_called_once_with(90.0) assert command.env["commandBuffer"]["lastProgressValue"] == 90.0 + + +@pytest.mark.unit +def test_progress_detector_beeps_for_interruptible_status_without_ellipsis(): + progress_module = _load_progress_module() + command = progress_module.command() + sample = "◦ Files: (1m 04s • esc to interrupt)" + command.env = { + "commandBuffer": { + "progress_monitoring": True, + "lastProgressValue": -1, + "lastProgressTime": 0, + }, + "runtime": { + "DebugManager": Mock(write_debug_out=Mock()), + "ScreenManager": Mock(is_screen_change=Mock(return_value=False)), + "CursorManager": Mock(is_cursor_vertical_move=Mock(return_value=False)), + }, + "screen": { + "new_delta": sample, + "new_delta_is_typing": False, + "new_content_text": sample, + "old_cursor": {"x": 0, "y": 0}, + "new_cursor": {"x": 0, "y": 0}, + }, + } + command.play_activity_beep = Mock() + + command.run() + + command.play_activity_beep.assert_called_once_with()