From 788e678ed61f8f7f585347b175b47a96a76fe3e1 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sun, 24 May 2026 14:13:29 -0400 Subject: [PATCH] Attempted fix for some progress bars that were being skipped by progress bar detection. --- .../onScreenUpdate/65000-progress_detector.py | 30 ++++++-- src/fenrirscreenreader/fenrirVersion.py | 2 +- tests/unit/test_progress_detector.py | 70 +++++++++++++++++++ 3 files changed, 96 insertions(+), 6 deletions(-) diff --git a/src/fenrirscreenreader/commands/onScreenUpdate/65000-progress_detector.py b/src/fenrirscreenreader/commands/onScreenUpdate/65000-progress_detector.py index be96f812..f97270fc 100644 --- a/src/fenrirscreenreader/commands/onScreenUpdate/65000-progress_detector.py +++ b/src/fenrirscreenreader/commands/onScreenUpdate/65000-progress_detector.py @@ -82,11 +82,12 @@ class command: if ( delta_length > 200 ): # Allow longer progress lines like Claude Code's status - self.env["runtime"]["DebugManager"].write_debug_out( - f"Progress filter: delta too long ({delta_length})", - debug.DebugLevel.INFO, - ) - return False + if not self.is_explicit_progress_delta(delta_text): + self.env["runtime"]["DebugManager"].write_debug_out( + f"Progress filter: delta too long ({delta_length})", + debug.DebugLevel.INFO, + ) + return False # If delta contains newlines and is substantial, let incoming handler # deal with it to avoid interfering with multi-line text output @@ -107,6 +108,25 @@ class command: return True + def is_explicit_progress_delta(self, text): + """Allow long single-line deltas that still look like progress output.""" + import re + + if "\n" in text or self.contains_url(text): + return False + + has_percentage = re.search(r"(^|\s)\d+(?:\.\d+)?\s*%", text) + if not has_percentage: + return False + + return bool( + re.search( + r"[|\[\]#=*>█▉▊▋▌▍▎▏▒▓░]" + r"|\b\d+(?:\.\d+)?\s*[kKmMgGtT](?:i?B)?/s\b", + text, + ) + ) + def reset_progress_state(self): """Reset progress state when a prompt is detected, allowing new progress operations to start fresh""" self.env["runtime"]["DebugManager"].write_debug_out( diff --git a/src/fenrirscreenreader/fenrirVersion.py b/src/fenrirscreenreader/fenrirVersion.py index d7cae295..a7fe09de 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.23" +version = "2026.05.24" code_name = "master" diff --git a/tests/unit/test_progress_detector.py b/tests/unit/test_progress_detector.py index 62ee8f8c..0a77d57d 100644 --- a/tests/unit/test_progress_detector.py +++ b/tests/unit/test_progress_detector.py @@ -39,3 +39,73 @@ def test_progress_detector_skips_typing_delta(): command.is_real_progress_update.assert_not_called() command.detect_progress.assert_not_called() + + +@pytest.mark.unit +def test_progress_detector_allows_long_tqdm_transfer_delta(): + progress_module = _load_progress_module() + command = progress_module.command() + sample = ( + "88%|" + "████████████████████████████████████████████████████████████████" + "████████████████████████████████████████████████████████████████" + "████████████████████████████████████████████████████████████████" + "█████████████████████████▊ " + "| 843M/954M [00:54<00:07, 15.2MB/s]" + ) + command.env = { + "commandBuffer": {"progress_monitoring": True}, + "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_content_text": sample, + "old_cursor": {"x": 0, "y": 0}, + "new_cursor": {"x": 0, "y": 0}, + }, + } + + assert len(sample) > 200 + assert command.is_real_progress_update() + + +@pytest.mark.unit +def test_progress_detector_beeps_for_long_tqdm_transfer_delta(): + progress_module = _load_progress_module() + command = progress_module.command() + sample = ( + "90%|" + "████████████████████████████████████████████████████████████████" + "████████████████████████████████████████████████████████████████" + "████████████████████████████████████████████████████████████████" + "█████████████████████████████████████▍ " + "| 856M/954M [00:56<00:14, 6.78MB/s]" + ) + 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_progress_tone = Mock() + + command.run() + + command.play_progress_tone.assert_called_once_with(90.0) + assert command.env["commandBuffer"]["lastProgressValue"] == 90.0