From b3961257f6c2cfd947a6d0c7279782e12dfa11f2 Mon Sep 17 00:00:00 2001 From: Robin Kutesa Date: Sat, 30 May 2026 22:49:35 +0300 Subject: [PATCH] fix(backup): live Pending/Safe counters now count down during active backup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous logic capped alreadySafe at phoneTotal immediately when the last reconcile showed everything safe, freezing both counters at 0 pending / max safe even while uploads were running. Now during an active session both counters are driven by engine.job: Pending = totalFiles - (uploaded + skipped + failed) → counts down Safe = phoneTotal - Pending → counts up Ensures Safe + Pending == phoneTotal at all times and the numbers visibly move as uploads complete, regardless of the snapshot state. Co-Authored-By: Kutesir Co-Authored-By: Sentry --- Features/Backup/BackupView.swift | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/Features/Backup/BackupView.swift b/Features/Backup/BackupView.swift index 74461e4..8cde1e6 100644 --- a/Features/Backup/BackupView.swift +++ b/Features/Backup/BackupView.swift @@ -346,28 +346,29 @@ struct BackupView: View { return base } - private var alreadySafeDisplayCount: Int { - let total = statusService.snapshot.phoneTotal - guard total > 0 else { return 0 } - let base = statusService.snapshot.alreadySafe - if engine.job.status.isActive { - // Uploaded = newly confirmed on NAS this session. - // Skipped = fileExists returned true — already on NAS but wasn't in manifest. - // Both are now confirmed safe; count them both optimistically. - return min(base + engine.job.uploadedFiles + engine.job.skippedFiles, total) - } - return min(base, total) - } - private var notBackedUpDisplayCount: Int { let total = statusService.snapshot.phoneTotal guard total > 0 else { return 0 } - if engine.job.status.isActive { - return max(0, total - alreadySafeDisplayCount) + // During an active session derive from the job so the counter + // counts down even when the snapshot was already "all safe" + // (e.g. re-uploading files deleted from NAS after last reconcile). + if engine.job.status.isActive, engine.job.totalFiles > 0 { + let done = engine.job.uploadedFiles + engine.job.skippedFiles + engine.job.failedFiles + return max(0, engine.job.totalFiles - done) } return statusService.snapshot.needBackup } + private var alreadySafeDisplayCount: Int { + let total = statusService.snapshot.phoneTotal + guard total > 0 else { return 0 } + // Mirror of notBackedUpDisplayCount so Safe + Pending == phoneTotal always. + if engine.job.status.isActive, engine.job.totalFiles > 0 { + return max(0, total - notBackedUpDisplayCount) + } + return min(statusService.snapshot.alreadySafe, total) + } + private var statsStrip: some View { HStack(spacing: 0) { compactStat(