Skip to content

Commit 3beec5b

Browse files
committed
feat: improve progress bar ui
1 parent 8593731 commit 3beec5b

1 file changed

Lines changed: 36 additions & 34 deletions

File tree

sysupdate/app.py

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
# ============================================================================
3535

3636
# Fixed width for description column (prefix + label + detail text)
37-
DESC_WIDTH = 24
37+
DESC_WIDTH = 30
3838

3939
# Progress thresholds - fraction of progress bar reserved for checking phase
4040
CHECKING_PROGRESS_END = 0.1
@@ -102,23 +102,13 @@ def render(self, task: RichTask) -> Text:
102102

103103

104104
class PhaseAwareProgressColumn(TaskProgressColumn):
105-
"""Task progress column that hides percentage during checking phase."""
105+
"""Task progress column that shows dim placeholder during indeterminate phase."""
106106

107107
def render(self, task: RichTask) -> Text:
108-
phase = task.fields.get("phase", "checking")
109-
if phase == "checking":
110-
return Text("", style="dim")
111-
return super().render(task)
112-
113-
114-
class PhaseAwareBarColumn(BarColumn):
115-
"""Bar column that shows pulse animation during checking phase."""
116-
117-
def render(self, task: RichTask) -> Text:
118-
phase = task.fields.get("phase", "checking")
119-
if phase == "checking":
120-
# Return empty/dim bar during checking
121-
return Text(" " * (self.bar_width or 16), style="dim")
108+
# When total is None (indeterminate), show placeholder to maintain width
109+
if task.total is None:
110+
# Match width of "100%" (4 chars)
111+
return Text(" - ", style="dim")
122112
return super().render(task)
123113

124114

@@ -133,13 +123,22 @@ def render(self, task: RichTask) -> Text:
133123

134124

135125
class ETAColumn(ProgressColumn):
136-
"""Shows ETA when available."""
126+
"""Shows ETA when available with fixed width."""
127+
128+
# Fixed width for ETA column (e.g., "ETA 10m30s" = 10 chars)
129+
ETA_WIDTH = 10
137130

138131
def render(self, task: RichTask) -> Text:
139132
eta = task.fields.get("eta", "")
140133
if eta:
141-
return Text(f"ETA {eta}", style="dim")
142-
return Text("", style="dim")
134+
text = f"ETA {eta}"
135+
# Pad or truncate to fixed width
136+
if len(text) < self.ETA_WIDTH:
137+
text = text + " " * (self.ETA_WIDTH - len(text))
138+
elif len(text) > self.ETA_WIDTH:
139+
text = text[:self.ETA_WIDTH]
140+
return Text(text, style="dim")
141+
return Text(" " * self.ETA_WIDTH, style="dim")
143142

144143

145144
class SysUpdateCLI:
@@ -230,16 +229,9 @@ def _format_desc(self, prefix: str, label: str, detail: str = "") -> str:
230229
# Pad to fixed width
231230
return text + " " * (DESC_WIDTH - visible_len)
232231
elif visible_len > DESC_WIDTH:
233-
# Truncate: find how much to trim from the end
234-
excess = visible_len - DESC_WIDTH
235-
# Remove markup, truncate, but keep the structure
236-
if detail:
237-
# Truncate detail portion
238-
detail_visible = _MARKUP_PATTERN.sub("", detail)
239-
if len(detail_visible) > excess:
240-
new_detail = detail_visible[:-excess-1] + "…"
241-
return f"{prefix}{label} [dim]{new_detail}[/]"
242-
return text[:DESC_WIDTH]
232+
# Truncate visible text and rebuild with markup
233+
truncated_visible = visible[:DESC_WIDTH - 1] + "…"
234+
return truncated_visible
243235
return text
244236

245237
def _create_progress_callback(
@@ -262,16 +254,16 @@ def on_progress(update: UpdateProgress) -> None:
262254
phase_value = update.phase.value if update.phase else "checking"
263255

264256
if update.phase == UpdatePhase.CHECKING:
265-
# During checking, show only spinner and message (no progress bar/percentage)
257+
# During checking, show pulse animation (don't set total/completed)
266258
if update.message:
267-
# Extract short status from message (increased limit to 25 chars)
259+
# Extract short status from message (limit to 25 chars)
268260
msg = update.message.rstrip(".")
269261
if len(msg) > 25:
270262
msg = msg[:24] + "…"
271263
desc = self._format_desc("", f"{label} [dim]|[/] {msg}")
272264
else:
273265
desc = self._format_desc("", f"{label} [dim]|[/] checking")
274-
# Don't update completed during checking - keeps bar indeterminate
266+
# Keep total=None for pulse animation, only update description
275267
progress.update(
276268
task_id,
277269
description=desc,
@@ -284,8 +276,10 @@ def on_progress(update: UpdateProgress) -> None:
284276
desc = self._format_desc("", f"{label} [dim]|[/] {pkg}")
285277
else:
286278
desc = self._format_desc("", f"{label} [dim]|[/] {phase_text}")
279+
# Transition to determinate progress: set total and completed
287280
progress.update(
288281
task_id,
282+
total=100,
289283
completed=pct,
290284
description=desc,
291285
phase=phase_value,
@@ -296,6 +290,7 @@ def on_progress(update: UpdateProgress) -> None:
296290
desc = self._format_desc("", label)
297291
progress.update(
298292
task_id,
293+
total=100,
299294
completed=pct,
300295
description=desc,
301296
phase=phase_value,
@@ -329,7 +324,7 @@ async def _run_updates(self) -> int:
329324
TextColumn(" "),
330325
StatusColumn(spinner_name="dots", style="white", use_ascii=self._use_ascii),
331326
TextColumn("{task.description}"),
332-
PhaseAwareBarColumn(bar_width=BAR_WIDTH, style="dim", complete_style="white", finished_style="green"),
327+
BarColumn(bar_width=BAR_WIDTH, style="dim", complete_style="white", finished_style="green", pulse_style="cyan"),
333328
PhaseAwareProgressColumn(),
334329
TimeElapsedColumn(),
335330
SpeedColumn(),
@@ -343,7 +338,12 @@ async def _run_updates(self) -> int:
343338

344339
for cfg, is_available in available_updaters:
345340
if is_available:
346-
task_id = progress.add_task(self._format_desc("", cfg.label), total=100)
341+
# Start with total=None for indeterminate pulse animation
342+
task_id = progress.add_task(
343+
self._format_desc("", cfg.label),
344+
total=None,
345+
phase="checking",
346+
)
347347
coroutines.append(self._run_updater(progress, task_id, cfg))
348348
labels.append(cfg.label)
349349
else:
@@ -385,8 +385,10 @@ async def _run_updater(
385385
dry_run=self.dry_run,
386386
)
387387

388+
# Ensure we transition to determinate mode and mark complete
388389
progress.update(
389390
task_id,
391+
total=100,
390392
completed=100,
391393
success=result.success,
392394
description=self._format_desc("", cfg.label)

0 commit comments

Comments
 (0)