Skip to content

🎨 Palette: Enhance CLI Summary Table and Progress Bar#326

Merged
abhimehro merged 5 commits intomainfrom
palette-ux-improvements-1798974858941889427
Feb 19, 2026
Merged

🎨 Palette: Enhance CLI Summary Table and Progress Bar#326
abhimehro merged 5 commits intomainfrom
palette-ux-improvements-1798974858941889427

Conversation

@abhimehro
Copy link
Owner

This PR enhances the CLI's visual presentation by:

  1. Refactoring the Progress Bar: Replaced the visual noise of the character with · (middle dot).
  2. Upgrading the Summary Table: Replaced the manual print statements with a structured table using Unicode box-drawing characters (or ASCII fallback). This improves readability and provides a more professional "report" feel at the end of execution.
  3. Tests: Updated tests/test_ux.py and verified all tests pass.

PR created automatically by Jules for task 1798974858941889427 started by @abhimehro

- Replaced the deprecated "light shade" character in the progress bar with a cleaner "middle dot" (`·`) character for reduced visual noise.
- Implemented a new `print_summary_table` function to display the sync results in a structured table format.
- Added support for Unicode box-drawing characters when colors are enabled, falling back to ASCII for compatibility.
- Fixed header alignment logic to ensure correct padding before applying ANSI color codes.
- Updated `tests/test_ux.py` to reflect the progress bar character change.

Co-authored-by: abhimehro <84992105+abhimehro@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 17, 2026 22:46
@google-labs-jules
Copy link

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@trunk-io
Copy link

trunk-io bot commented Feb 17, 2026

😎 Merged manually by @abhimehro - details.

@github-actions
Copy link

👋 Development Partner is reviewing this PR. Will provide feedback shortly.

@gemini-code-assist
Copy link

Summary of Changes

Hello @abhimehro, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the command-line interface's visual presentation by introducing a more professional and structured summary table. It also refines the progress bar's appearance for a cleaner user experience. These changes enhance the overall clarity and aesthetic appeal of the CLI output, making it easier for users to digest information.

Highlights

  • CLI Summary Table Enhancement: The manual print statements for the CLI summary table have been replaced with a dedicated print_summary_table function. This new function generates a structured table using Unicode box-drawing characters (with an ASCII fallback) and includes dynamic column sizing, improved formatting, and color support for better readability.
  • Progress Bar Refactoring: The visual character used for the unfilled portion of the progress bar has been updated from (light shade block) to · (middle dot) in both countdown_timer and render_progress_bar functions, aiming to reduce visual noise.
  • Test Updates: Existing unit tests in tests/test_ux.py were updated to reflect the change in the progress bar character, ensuring continued correctness of the CLI's visual components.
Changelog
  • main.py
    • Added a new function print_summary_table to generate a formatted summary table with Unicode box-drawing characters and color support.
    • Removed the previous manual summary table printing logic from the main execution flow.
    • Updated the progress bar character from to · in countdown_timer and render_progress_bar functions.
  • tests/test_ux.py
    • Modified test_countdown_timer_visuals to assert the presence of the new progress bar character · instead of .
Activity
  • The pull request was automatically created by Jules for task 1798974858941889427, initiated by @abhimehro.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.


# Check for progress bar chars
assert "" in combined_output
assert "·" in combined_output

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request significantly enhances the CLI's visual output by refactoring the progress bar and introducing a well-formatted summary table. The move to a dedicated print_summary_table function is a great improvement for code organization. My review focuses on opportunities within this new function to further improve maintainability by reducing code duplication. The changes to the progress bar and the corresponding test updates look good.

Comment on lines +370 to +376
total_width = (
1 + (col_widths["profile"] + 2) + 1 +
(col_widths["folders"] + 2) + 1 +
(col_widths["rules"] + 2) + 1 +
(col_widths["duration"] + 2) + 1 +
(col_widths["status"] + 2) + 1
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The calculation for total_width is repetitive and can be simplified by deriving it from the col_widths dictionary. This makes the code more maintainable, as it will automatically adjust if columns are added or removed.

    total_width = sum(col_widths.values()) + (len(col_widths) * 3) + 1

Comment on lines +390 to +458
for res in results:
# Profile
p_val = f"{res['profile']:<{col_widths['profile']}}"

# Folders
f_val = f"{res['folders']:>{col_widths['folders']}}"

# Rules
r_val = f"{res['rules']:>{col_widths['rules']},}"

# Duration
d_val = f"{res['duration']:>{col_widths['duration']-1}.1f}s"

# Status
status_label = res["status_label"]
s_val_raw = f"{status_label:<{col_widths['status']}}"
if USE_COLORS:
status_color = Colors.GREEN if res["success"] else Colors.FAIL
s_val = f"{status_color}{s_val_raw}{Colors.ENDC}"
else:
s_val = s_val_raw

print(
f"{chars['v']} {p_val} "
f"{chars['v']} {f_val} "
f"{chars['v']} {r_val} "
f"{chars['v']} {d_val} "
f"{chars['v']} {s_val} {chars['v']}"
)

total_folders += res["folders"]
total_rules += res["rules"]
total_duration += res["duration"]
if res["success"]:
success_count += 1

_print_separator("ml", "mm", "mr")

# Total Row
total = len(results)
all_success = success_count == total

if dry_run:
total_status_text = "✅ Ready" if all_success else "❌ Errors"
else:
total_status_text = "✅ All Good" if all_success else "❌ Errors"

p_val = f"{'TOTAL':<{col_widths['profile']}}"
if USE_COLORS:
p_val = f"{Colors.BOLD}{p_val}{Colors.ENDC}"

f_val = f"{total_folders:>{col_widths['folders']}}"
r_val = f"{total_rules:>{col_widths['rules']},}"
d_val = f"{total_duration:>{col_widths['duration']-1}.1f}s"

s_val_raw = f"{total_status_text:<{col_widths['status']}}"
if USE_COLORS:
status_color = Colors.GREEN if all_success else Colors.FAIL
s_val = f"{status_color}{s_val_raw}{Colors.ENDC}"
else:
s_val = s_val_raw

print(
f"{chars['v']} {p_val} "
f"{chars['v']} {f_val} "
f"{chars['v']} {r_val} "
f"{chars['v']} {d_val} "
f"{chars['v']} {s_val} {chars['v']}"
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There's significant code duplication in how data rows and the total row are formatted and printed. The logic within the for res in results: loop is nearly identical to the logic for printing the 'TOTAL' row.

To improve maintainability and adhere to the DRY (Don't Repeat Yourself) principle, this shared logic should be extracted into a dedicated helper function. This function could handle the formatting, coloring, and printing of a data row, and be called from both the loop and for the total row. The existing _print_row function is only used for the header and is not suitable for data rows due to different formatting requirements.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the CLI's visual presentation by replacing the progress bar's light shade character (░) with a cleaner middle dot (·) and refactoring the summary table to use Unicode box-drawing characters (with ASCII fallback). The changes improve readability and provide a more professional appearance for the application's output.

Changes:

  • Replaced progress bar unfilled character from to · in both countdown_timer and render_progress_bar functions
  • Extracted summary table logic from main() into a new print_summary_table() function with structured box-drawing characters
  • Updated test expectations to verify the new progress bar character

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
main.py Added print_summary_table() function with Unicode/ASCII box-drawing support; updated progress bar characters in countdown_timer and render_progress_bar; removed inline table printing logic from main()
tests/test_ux.py Updated test assertion to check for new middle dot character instead of light shade in progress bar output

Comment on lines +298 to +460
def print_summary_table(results: List[Dict[str, Any]], dry_run: bool) -> None:
"""Prints a nicely formatted summary table."""
# Determine the width for the Profile ID column (min 25)
max_profile_len = max((len(r["profile"]) for r in results), default=25)
profile_col_width = max(25, max_profile_len)

# Calculate widths
col_widths = {
"profile": profile_col_width,
"folders": 10,
"rules": 10,
"duration": 10,
"status": 15,
}

if USE_COLORS:
# Unicode Box Drawing
chars = {
"tl": "┌", "tm": "┬", "tr": "┐",
"bl": "└", "bm": "┴", "br": "┘",
"ml": "├", "mm": "┼", "mr": "┤",
"v": "│", "h": "─",
}
else:
# ASCII Fallback
chars = {
"tl": "+", "tm": "+", "tr": "+",
"bl": "+", "bm": "+", "br": "+",
"ml": "+", "mm": "+", "mr": "+",
"v": "|", "h": "-",
}

def _print_separator(left, mid, right):
parts = []
parts.append(chars[left])
parts.append(chars["h"] * (col_widths["profile"] + 2))
parts.append(chars[mid])
parts.append(chars["h"] * (col_widths["folders"] + 2))
parts.append(chars[mid])
parts.append(chars["h"] * (col_widths["rules"] + 2))
parts.append(chars[mid])
parts.append(chars["h"] * (col_widths["duration"] + 2))
parts.append(chars[mid])
parts.append(chars["h"] * (col_widths["status"] + 2))
parts.append(chars[right])
print("".join(parts))

def _print_row(profile, folders, rules, duration, status, is_header=False):
v = chars["v"]

# 1. Pad raw strings first (so padding is calculated on visible chars)
p_val = f"{profile:<{col_widths['profile']}}"
f_val = f"{folders:>{col_widths['folders']}}"
r_val = f"{rules:>{col_widths['rules']}}"
d_val = f"{duration:>{col_widths['duration']}}"
s_val = f"{status:<{col_widths['status']}}"

# 2. Wrap in color codes if needed
if is_header and USE_COLORS:
p_val = f"{Colors.BOLD}{p_val}{Colors.ENDC}"
f_val = f"{Colors.BOLD}{f_val}{Colors.ENDC}"
r_val = f"{Colors.BOLD}{r_val}{Colors.ENDC}"
d_val = f"{Colors.BOLD}{d_val}{Colors.ENDC}"
s_val = f"{Colors.BOLD}{s_val}{Colors.ENDC}"

print(
f"{v} {p_val} {v} {f_val} {v} {r_val} {v} {d_val} {v} {s_val} {v}"
)

title_text = "DRY RUN SUMMARY" if dry_run else "SYNC SUMMARY"
title_color = Colors.CYAN if dry_run else Colors.HEADER

total_width = (
1 + (col_widths["profile"] + 2) + 1 +
(col_widths["folders"] + 2) + 1 +
(col_widths["rules"] + 2) + 1 +
(col_widths["duration"] + 2) + 1 +
(col_widths["status"] + 2) + 1
)

print("\n" + (f"{title_color}{title_text:^{total_width}}{Colors.ENDC}" if USE_COLORS else f"{title_text:^{total_width}}"))

_print_separator("tl", "tm", "tr")
# Header row - pad manually then print
_print_row("Profile ID", "Folders", "Rules", "Duration", "Status", is_header=True)
_print_separator("ml", "mm", "mr")

total_folders = 0
total_rules = 0
total_duration = 0.0
success_count = 0

for res in results:
# Profile
p_val = f"{res['profile']:<{col_widths['profile']}}"

# Folders
f_val = f"{res['folders']:>{col_widths['folders']}}"

# Rules
r_val = f"{res['rules']:>{col_widths['rules']},}"

# Duration
d_val = f"{res['duration']:>{col_widths['duration']-1}.1f}s"

# Status
status_label = res["status_label"]
s_val_raw = f"{status_label:<{col_widths['status']}}"
if USE_COLORS:
status_color = Colors.GREEN if res["success"] else Colors.FAIL
s_val = f"{status_color}{s_val_raw}{Colors.ENDC}"
else:
s_val = s_val_raw

print(
f"{chars['v']} {p_val} "
f"{chars['v']} {f_val} "
f"{chars['v']} {r_val} "
f"{chars['v']} {d_val} "
f"{chars['v']} {s_val} {chars['v']}"
)

total_folders += res["folders"]
total_rules += res["rules"]
total_duration += res["duration"]
if res["success"]:
success_count += 1

_print_separator("ml", "mm", "mr")

# Total Row
total = len(results)
all_success = success_count == total

if dry_run:
total_status_text = "✅ Ready" if all_success else "❌ Errors"
else:
total_status_text = "✅ All Good" if all_success else "❌ Errors"

p_val = f"{'TOTAL':<{col_widths['profile']}}"
if USE_COLORS:
p_val = f"{Colors.BOLD}{p_val}{Colors.ENDC}"

f_val = f"{total_folders:>{col_widths['folders']}}"
r_val = f"{total_rules:>{col_widths['rules']},}"
d_val = f"{total_duration:>{col_widths['duration']-1}.1f}s"

s_val_raw = f"{total_status_text:<{col_widths['status']}}"
if USE_COLORS:
status_color = Colors.GREEN if all_success else Colors.FAIL
s_val = f"{status_color}{s_val_raw}{Colors.ENDC}"
else:
s_val = s_val_raw

print(
f"{chars['v']} {p_val} "
f"{chars['v']} {f_val} "
f"{chars['v']} {r_val} "
f"{chars['v']} {d_val} "
f"{chars['v']} {s_val} {chars['v']}"
)

_print_separator("bl", "bm", "br")
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new print_summary_table function lacks test coverage despite the codebase having comprehensive test coverage for similar display functions (see test_plan_details.py for examples). Tests should verify:

  1. Correct table formatting with and without colors
  2. Proper handling of Unicode vs ASCII box-drawing characters based on USE_COLORS
  3. Accurate calculation and display of totals
  4. Correct status indicators for dry-run vs real sync modes
  5. Edge cases like empty results list or very long profile names

Copilot uses AI. Check for mistakes.
Comment on lines +437 to +450
p_val = f"{'TOTAL':<{col_widths['profile']}}"
if USE_COLORS:
p_val = f"{Colors.BOLD}{p_val}{Colors.ENDC}"

f_val = f"{total_folders:>{col_widths['folders']}}"
r_val = f"{total_rules:>{col_widths['rules']},}"
d_val = f"{total_duration:>{col_widths['duration']-1}.1f}s"

s_val_raw = f"{total_status_text:<{col_widths['status']}}"
if USE_COLORS:
status_color = Colors.GREEN if all_success else Colors.FAIL
s_val = f"{status_color}{s_val_raw}{Colors.ENDC}"
else:
s_val = s_val_raw
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TOTAL row formatting differs from the old implementation. In the old code (removed lines), the entire TOTAL row was wrapped in bold (except the status which had its own color). In the new implementation, only the "TOTAL" text in the profile column is bolded (lines 437-439), while the numeric values (folders, rules, duration) are not bolded.

This changes the visual appearance of the summary table's total row. Consider wrapping all columns except status in bold to match the previous behavior, or update lines 441-443 to also be wrapped in bold when USE_COLORS is True.

Copilot uses AI. Check for mistakes.
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@github-actions
Copy link

👋 Development Partner is reviewing this PR. Will provide feedback shortly.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actions
Copy link

👋 Development Partner is reviewing this PR. Will provide feedback shortly.

@github-actions
Copy link

👋 Development Partner is reviewing this PR. Will provide feedback shortly.

@github-actions
Copy link

👋 Development Partner is reviewing this PR. Will provide feedback shortly.

@abhimehro abhimehro merged commit af5fd1b into main Feb 19, 2026
8 of 11 checks passed
@abhimehro abhimehro deleted the palette-ux-improvements-1798974858941889427 branch February 19, 2026 02:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants