From e31693f44fc6c757bae4f20597221d410899cdfa Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 23:07:11 +0000 Subject: [PATCH] feat: add progress bar for cache warming Added a `ProgressBar` class to providing visual feedback during the URL pre-fetching phase. This improves UX by replacing a static wait with an active indicator. - Implemented `ProgressBar` class with TTY support. - Updated `warm_up_cache` to use the progress bar. - Added `clear` method to handle log interleaving cleanly. --- .jules/palette.md | 4 ++++ main.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/.jules/palette.md b/.jules/palette.md index fbedb30..e84f00d 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -14,3 +14,7 @@ ## 2024-03-22 - CLI Interactive Fallbacks **Learning:** CLI tools often fail hard when config is missing, but interactive contexts allow for graceful recovery. Users appreciate being asked for missing info instead of just receiving an error. **Action:** When `sys.stdin.isatty()` is true, prompt for missing configuration instead of exiting with an error code. + +## 2025-06-15 - Managing Visual Noise +**Learning:** Adding progress bars to verbose/chatty CLI processes (streams of logs) creates visual clutter and flickering. It's better to keep progress bars for "silent" wait states (like cache warming) and leave verbose processes as streams. +**Action:** Only implement progress bars for phases that are otherwise silent or have low log volume; avoid them for high-frequency logging loops. diff --git a/main.py b/main.py index e6aabc5..b247a21 100644 --- a/main.py +++ b/main.py @@ -59,6 +59,37 @@ class Colors: BOLD = '' UNDERLINE = '' +class ProgressBar: + """A simple CLI progress bar.""" + def __init__(self, total: int, prefix: str = "Progress", length: int = 30): + self.total = total + self.prefix = prefix + self.length = length + self.current = 0 + + def increment(self): + self.current += 1 + self._print() + + def clear(self): + if USE_COLORS: + sys.stderr.write("\r\033[K") + sys.stderr.flush() + + def _print(self): + if not USE_COLORS or self.total == 0: + return + + percent = float(self.current) / self.total + filled = int(self.length * percent) + bar = '█' * filled + '-' * (self.length - filled) + + sys.stderr.write(f"\r{Colors.CYAN}{self.prefix} |{bar}| {self.current}/{self.total}{Colors.ENDC}") + sys.stderr.flush() + + if self.current == self.total: + sys.stderr.write("\n") + class ColoredFormatter(logging.Formatter): """Custom formatter to add colors to log levels.""" LEVEL_COLORS = { @@ -387,11 +418,15 @@ def warm_up_cache(urls: Sequence[str]) -> None: log.info(f"Warming up cache for {len(urls_to_fetch)} URLs...") with concurrent.futures.ThreadPoolExecutor() as executor: futures = {executor.submit(_gh_get, url): url for url in urls_to_fetch} + pbar = ProgressBar(len(futures), prefix="Fetching") for future in concurrent.futures.as_completed(futures): try: future.result() except Exception as e: + pbar.clear() log.warning(f"Failed to pre-fetch {sanitize_for_log(futures[future])}: {e}") + finally: + pbar.increment() def delete_folder(client: httpx.Client, profile_id: str, name: str, folder_id: str) -> bool: try: