From 0c1a2c6c4669248dec2ad7ae111afafe6a1059a7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 23:05:54 +0000 Subject: [PATCH 1/5] feat(ux): Add visual progress bar to countdown timer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replaced simple countdown with ASCII progress bar [██░░] - Used ANSI clear-line code for cleaner updates - Added tests for UX visuals in `tests/test_ux.py` - Updated journal with CLI learnings --- .Jules/palette.md | 4 ++++ main.py | 8 ++++++-- tests/test_ux.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 tests/test_ux.py diff --git a/.Jules/palette.md b/.Jules/palette.md index 13f6528..4e317ea 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -1,3 +1,7 @@ ## 2024-05-22 - Helpful CLI Prompts **Learning:** Even in CLI tools, users often get stuck on authentication steps (Tokens/IDs). Providing direct URLs or location hints in the prompt text significantly reduces friction compared to forcing users to consult external docs. **Action:** When prompting for credentials in CLI tools, always include a "Where to find this" hint or direct URL. + +## 2024-05-23 - CLI Progress Bars +**Learning:** Using clear-line ANSI codes (`\033[K`) is significantly more robust than space-padding for overwriting CLI lines, especially when line lengths vary between updates. Visual progress bars (e.g., `[██░░]`) provide better psychological feedback for waiting periods than simple countdowns. +**Action:** Use `\033[K` for dynamic CLI updates and favor visual bars for waits > 5 seconds. diff --git a/main.py b/main.py index b6bbb8b..d4bb05b 100644 --- a/main.py +++ b/main.py @@ -117,12 +117,16 @@ def countdown_timer(seconds: int, message: str = "Waiting") -> None: time.sleep(seconds) return + width = 15 for remaining in range(seconds, 0, -1): - sys.stderr.write(f"\r{Colors.CYAN}⏳ {message}: {remaining}s...{Colors.ENDC}") + progress = (seconds - remaining) / seconds + filled = int(width * progress) + bar = "█" * filled + "░" * (width - filled) + sys.stderr.write(f"\r{Colors.CYAN}⏳ {message}: [{bar}] {remaining}s...{Colors.ENDC}") sys.stderr.flush() time.sleep(1) - sys.stderr.write(f"\r{Colors.GREEN}✅ {message}: Done! {Colors.ENDC}\n") + sys.stderr.write(f"\r\033[K{Colors.GREEN}✅ {message}: Done!{Colors.ENDC}\n") sys.stderr.flush() diff --git a/tests/test_ux.py b/tests/test_ux.py new file mode 100644 index 0000000..afb2187 --- /dev/null +++ b/tests/test_ux.py @@ -0,0 +1,47 @@ + +import sys +import pytest +from unittest.mock import MagicMock, call, patch +import main + +def test_countdown_timer_visuals(monkeypatch): + """Verify that countdown_timer writes a progress bar to stderr.""" + # Force colors on + monkeypatch.setattr(main, "USE_COLORS", True) + + # Mock stderr + mock_stderr = MagicMock() + monkeypatch.setattr(sys, "stderr", mock_stderr) + + # Mock time.sleep to run instantly + monkeypatch.setattr(main.time, "sleep", MagicMock()) + + main.countdown_timer(3, "Test") + + # Check calls + writes = [args[0] for args, _ in mock_stderr.write.call_args_list] + combined_output = "".join(writes) + + # Check for progress bar chars + assert "░" in combined_output + assert "█" in combined_output + assert "Test" in combined_output + assert "Done!" in combined_output + + # Check for ANSI clear line code + assert "\033[K" in combined_output + +def test_countdown_timer_no_colors(monkeypatch): + """Verify that countdown_timer sleeps without writing to stderr if NO_COLOR.""" + monkeypatch.setattr(main, "USE_COLORS", False) + mock_stderr = MagicMock() + monkeypatch.setattr(sys, "stderr", mock_stderr) + mock_sleep = MagicMock() + monkeypatch.setattr(main.time, "sleep", mock_sleep) + + main.countdown_timer(3, "Test") + + # Should not write to stderr + mock_stderr.write.assert_not_called() + # Should call sleep with full seconds + mock_sleep.assert_called_with(3) From fc5e28944516e19d952e38db0016ee1d5b80d0dc Mon Sep 17 00:00:00 2001 From: Abhi Mehrotra Date: Mon, 19 Jan 2026 21:16:40 -0600 Subject: [PATCH 2/5] Update main.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index d4bb05b..9f324ef 100644 --- a/main.py +++ b/main.py @@ -119,7 +119,7 @@ def countdown_timer(seconds: int, message: str = "Waiting") -> None: width = 15 for remaining in range(seconds, 0, -1): - progress = (seconds - remaining) / seconds + progress = (seconds - remaining + 1) / seconds filled = int(width * progress) bar = "█" * filled + "░" * (width - filled) sys.stderr.write(f"\r{Colors.CYAN}⏳ {message}: [{bar}] {remaining}s...{Colors.ENDC}") From 5b5fe9419700ca167089997890bf9839ccaafca3 Mon Sep 17 00:00:00 2001 From: Abhi Mehrotra Date: Mon, 19 Jan 2026 21:16:49 -0600 Subject: [PATCH 3/5] Update test_ux.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/test_ux.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_ux.py b/tests/test_ux.py index afb2187..ae111ad 100644 --- a/tests/test_ux.py +++ b/tests/test_ux.py @@ -43,5 +43,5 @@ def test_countdown_timer_no_colors(monkeypatch): # Should not write to stderr mock_stderr.write.assert_not_called() - # Should call sleep with full seconds - mock_sleep.assert_called_with(3) + # Should call sleep exactly once with full seconds + mock_sleep.assert_called_once_with(3) From bfda5695e84d876b43512e3ab2cc93a8efa4df69 Mon Sep 17 00:00:00 2001 From: Abhi Mehrotra Date: Mon, 19 Jan 2026 21:16:57 -0600 Subject: [PATCH 4/5] Update test_ux.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/test_ux.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_ux.py b/tests/test_ux.py index ae111ad..6f23912 100644 --- a/tests/test_ux.py +++ b/tests/test_ux.py @@ -1,6 +1,5 @@ import sys -import pytest from unittest.mock import MagicMock, call, patch import main From 0b3c7c5f557e9be22e7b3da549373d346641d234 Mon Sep 17 00:00:00 2001 From: Abhi Mehrotra Date: Mon, 19 Jan 2026 21:17:03 -0600 Subject: [PATCH 5/5] Update test_ux.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/test_ux.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_ux.py b/tests/test_ux.py index 6f23912..311b7e9 100644 --- a/tests/test_ux.py +++ b/tests/test_ux.py @@ -1,6 +1,6 @@ import sys -from unittest.mock import MagicMock, call, patch +from unittest.mock import MagicMock import main def test_countdown_timer_visuals(monkeypatch):