Show real progress and a completion notice for manual backups - #582
Merged
Merged
Conversation
PDFDocumentRenderer.render now takes a progress handler, called after each image is drawn with the fraction of images processed. The two-pass vault renderer maps each pass onto half of the range, and VaultBackupPDFGenerator.makePDF exposes it with a no-op default so existing callers are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
.backingUp now carries an AutoBackupProgress (phase + fraction). The service exports on the main actor, then encrypts and renders in a nonisolated child task so the UI can repaint; progress flows back through an AsyncStream so every update is applied, in order, before the status moves on to saving/completed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
performBackup suspends several times and had no in-flight guard, so a debounced auto-backup (or toggling the feature on) during a manual backup ran concurrently: two files written and two progress sequences fighting over the status. Backups now queue behind the previous one. triggerBackupIfNeeded re-checks the payload hash once it is its turn, so a change made during an in-flight backup is still picked up. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Backup Now row now follows the service status: a phase label and determinate bar while backing up, a cleanup row, then a brief "Backup Complete" confirmation before the button returns. The notice is owned by the view model and keyed off the transition out of a real backup, so the sticky completed status shown on screen open, or the cleanup that follows a retention change, does not trigger it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
AutoBackupView): was anAsyncButtonthat disabled, flashed an indeterminate spinner after 150 ms, then re-enabled with no feedback. It now follows the service status: a phase label ("Exporting vault…" / "Encrypting…" / "Rendering QR codes…" / "Saving…") with a determinateProgressView, a "Cleaning up old backups…" row, then a green "Backup Complete" row for ~2 s before the button returns. Auto-triggered backups show the same row.PDFDocumentRenderer.rendergains a progress handler called after each QR code is drawn (PDFDataBlockDocumentRenderer); the two-passVaultExportPDFDocumentRenderermaps each pass onto half the range;VaultBackupPDFGenerator.makePDFexposes it with a no-op default soBackupCreatePDFViewModelis untouched. Rendering QR codes (freshCIContextper code, rendered twice) is ~90 % of a backup, so this is where the bar actually moves.AutoBackupStatus.backingUp(AutoBackupProgress): newAutoBackupProgress { phase, phaseFraction, fractionCompleted }with fixed per-phase weights so the bar advances at a steady pace.@MainActor, so nothing could repaint mid-backup.AutoBackupServiceImplnow exports on main, then encrypts + renders in anonisolatedchild task (same pattern asBackupCreatePDFViewModel.makeExportPayload). Progress comes back through anAsyncStreamconsumed withasync let, so every update is applied in order before the status moves on — a late.backingUpcan never overwrite.completed.performBackupsuspends several times with no in-flight guard, so a debounced auto-backup (or toggling the feature on) during a manual backup ran concurrently: two files, two progress sequences fighting overstatus. Now queued FIFO;triggerBackupIfNeededre-checks the payload hash when it is its turn so a change made during an in-flight backup is still picked up. Separate commit for bisectability..completed(lastBackupDate)shown on screen open, or the.cleaningUp → .completedthat follows a retention change, does not show it.Manifesto: progress text is phase names only — no item content, no counts by lock/killphrase state, no new logging.
Follow-ups not in scope:
QRCodeImageRenderercreates aCIContextper code (reusable) and the document is rendered twice with fresh random shard group IDs in release, so the first pass can't be cached — both are the real fix for backup duration.BackupCreatePDFViewModelstill renders on main; the same offload applies.Test plan
make format/make lintPDFDataBlockDocumentRendererUnitTests: progress per image, across multiple data blocks, empty document reports 1, no progress on failureVaultExportPDFDocumentRendererTests: first pass → 0…0.5, second → 0.5…1AutoBackupProgressTests(range mapping, continuity across phases, clamping)AutoBackupServiceImplTests: ordered progress ending in.completed; overlappingforceBackups run sequentially;triggerBackupIfNeededduring an in-flight backup re-checks the hash (both verified to fail without the queue)AutoBackupViewModelTests:backupProgress, completion notice show/hide/timeout, not shown without a backup or after an errorAutoBackupViewSnapshotTestsbaselines:backingUp,cleaningUp,backupComplete; existing baselines unchangedVaultExportTests,VaultBackupTests,VaultFeedTests,VaultiOSTestsgreen on iPhone 18 Pro Max / iOS 27.0-screenshot-scene backups): tapped Backup Now, captured "Rendering QR codes…" with the bar ~60 % mid-backup, then "Backup Complete", then the button back with footer "Last backup: …". No freeze, no spinner flash.🤖 Generated with Claude Code