819f2b949ea5386185a08690fa75e5b3f583a07e
BackupJob: fix finish() — failedCount > 0 now correctly sets .failed instead of .completed in both branches. BackupEngine: replace filename-only fileExists check with a 3-layer dedup strategy. For each asset: (1) check ManifestIndex by localIdentifier O(1); (2) filename fallback for bootstrap manifests; (3) NAS fileExists for files not yet in manifest. Never overwrite an existing NAS file. Manifest index is built once after connect and held in a local let for the loop — Data+BackupManifest are released immediately after. BackupView — ring color: active (running/preparing/paused) → orange always failed → destructive needBackup == 0 && phoneTotal > 0 → green default → neutral gray Ring is no longer permanently green after .completed. BackupView — CTA button: Default color is always black (AppTheme.ink). On .completed: briefly flashes green for 2.5 s then smoothly reverts to black. ctaFlashGreen state drives the color. BackupView — Already Safe invariant: alreadySafeDisplayCount = min(base + uploadedFiles, phoneTotal) during active backup; min(base, phoneTotal) otherwise. Adding skippedFiles was removed — they are already counted in snapshot.alreadySafe from the last reconcile. notBackedUpDisplayCount is derived from alreadySafeDisplayCount to keep the invariant alreadySafe + needBackup == phoneTotal. Co-Authored-By: Kutesir <kutesir@provoc.ug> Co-Authored-By: Sentry <sentry@provoc.ug>
Description
Privacy-first iOS photo backup to NAS — SMB + SFTP, auto LAN trigger
Languages
Swift
100%