From 9f17ba6867fe97613931f377ae2980eafba37e88 Mon Sep 17 00:00:00 2001 From: archipelago Date: Fri, 26 Jun 2026 06:04:48 -0400 Subject: [PATCH] fix(ui): truthful uninstall progress bar (was a solid full-red block) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AppCard's uninstall bar was hardcoded `w-full bg-red-400/60 animate-pulse` — a solid, full-width, red, fake-pulsing block that never moved and read as an error, no matter the actual teardown progress (the install bar, by contrast, renders a real percentage). Derive a truthful percentage from the backend's existing `uninstall-stage` label — "Stopping containers (X/N)" → 10–50%, "Cleaning up volumes" → 70%, "Removing app data" → 90% — and render it exactly like install: neutral fill, real width + percent, shimmer (not a fake pulse) carrying motion when a stage has no number. Frontend-only; the backend already broadcasts these stages. Co-Authored-By: Claude Opus 4.8 (1M context) --- neode-ui/src/views/apps/AppCard.vue | 47 +++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/neode-ui/src/views/apps/AppCard.vue b/neode-ui/src/views/apps/AppCard.vue index 1c2469fe..c7543ac6 100644 --- a/neode-ui/src/views/apps/AppCard.vue +++ b/neode-ui/src/views/apps/AppCard.vue @@ -102,17 +102,23 @@ - +
-
- - - - - {{ uninstallStageLabel }} +
+ + + + + + {{ uninstallStageLabel }} + + {{ uninstallProgress }}%
-
-
+
+
@@ -282,6 +288,29 @@ const uninstallStageLabel = computed(() => { return raw ? raw : `${t('common.uninstalling')}…` }) +// Map the backend's uninstall-stage label to a truthful percentage so the bar +// progresses through the teardown instead of sitting at a solid full(-red) +// block. Backend stages (set_uninstall_stage): +// "Stopping containers (X/N)" → 10–50% (linear over the stack) +// "Cleaning up volumes" → 70% +// "Removing app data" → 90% +// Unknown/between pushes → null → the bar parks low and the shimmer overlay +// (install-progress-fill) carries the motion, exactly like a fixed install phase. +const uninstallProgress = computed(() => { + const raw = props.pkg['uninstall-stage'] || '' + const m = raw.match(/\((\d+)\s*\/\s*(\d+)\)/) + if (m) { + const done = Number(m[1]) + const total = Number(m[2]) + if (total > 0) { + return Math.round(10 + Math.min(done / total, 1) * 40) + } + } + if (/volume/i.test(raw)) return 70 + if (/data/i.test(raw)) return 90 + return null +}) + const isTransitioning = computed(() => { const s = props.pkg.state const h = props.pkg.health