Where: import/export backup (whole-database .zip)#11
Open
kyleve wants to merge 7 commits into
Open
Conversation
Backup groundwork: extend WhereStore with allEvidence()/allManualDays() (predicate-less reads) and clearAll() (full wipe for the replace import strategy), implement them in SwiftDataStore, make DayPresence Codable so it can ride in the backup manifest, and keep the ToggleFailingStore test double conforming. Closes plan step: store-readers. Co-authored-by: Cursor <cursoragent@cursor.com>
Backup export/import needs a real .zip reader/writer; Foundation has no public unzip API. Pin ZIPFoundation 0.9.20 (same external-SPM pattern as swift-snapshot-testing) and link it into the WhereCore target. Unused until the BackupService step; committed on its own as groundwork. Closes plan step: package-dep. Co-authored-by: Cursor <cursoragent@cursor.com>
Define the versioned, Codable BackupArchive (samples + evidence + manual days + an asset index) and BackupService, which marshals a whole-database backup to a deflate-compressed .zip (manifest.json + assets/<evidence-id>) and reads one back into value types + blob bytes. Covered by round-trip tests for the manifest JSON, the full zip, and a non-zip rejection. Closes plan step: backup-types. Co-authored-by: Cursor <cursoragent@cursor.com>
Expose the whole-database backup entry points: exportBackup() reads all three tables plus evidence blobs and hands them to BackupService; importBackup(from:strategy:) reads an archive (bracketing the security-scoped document-picker URL) and writes it back in one perform transaction, with .replace clearing the store first and .merge relying on upsert semantics. ImportSummary reports the row counts. Covered by merge/replace round-trip tests and a clearAll table-wipe test. Closes plan step: controller-api. Co-authored-by: Cursor <cursoragent@cursor.com>
Add exportBackup()/importBackup(from:strategy:) plus a BackupState (idle/exporting/importing) and a backupError channel so the Settings UI can show progress, present the share sheet, and surface failures. Keep the TestStore double conforming to the extended WhereStore. Covered by a two-store round-trip and a bogus-file failure test. Closes plan step: model-api. Co-authored-by: Cursor <cursoragent@cursor.com>
New Backup section above the erase action: Export builds the .zip and presents it in a UIActivityViewController share sheet (email / AirDrop / Files), cleaning up the temp file on dismiss; Import opens a .zip via fileImporter, then a merge/replace confirmation dialog, and reports a success summary or error alert. Both rows show a spinner and disable while work is in flight. Adds the settings.backup.* string catalog entries and Strings accessors. Closes plan step: settings-ui. Co-authored-by: Cursor <cursoragent@cursor.com>
Round out the backup test coverage (BackupService, controller merge/ replace round-trips, clearAll, and the WhereModel bridge were added with their respective steps): assert the new settings.backup.* catalog keys resolve, and that the imported-summary message substitutes sample / evidence / manual-day counts in the right order. Settings hosting stays covered by the existing settingsViewHosts() test, which now builds the new Backup section. Closes plan step: tests. Co-authored-by: Cursor <cursoragent@cursor.com>
kyleve
commented
Jun 5, 2026
| /// Serialize the entire store (all three tables plus evidence blobs) to a | ||
| /// `.zip` in the temporary directory and return its URL. The caller owns | ||
| /// the file: share it, then delete it (or its parent directory). | ||
| public func exportBackup() async throws -> URL { |
Owner
Author
There was a problem hiding this comment.
Does this block the main thread at all? Or does it run on the background?
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
Adds a whole-database backup feature to the Where app, surfaced from a new Backup section in the Settings tab.
.zip, then share it via the system share sheet (email / AirDrop / Files)..zip, choosing Merge (upsert into existing data) or Replace all (wipe first, then restore) at import time.Archive format
Layering (UI never touches SwiftData)
WhereStore/SwiftDataStorewithallEvidence(),allManualDays(),clearAll(); makeDayPresenceCodable; addBackupArchive+BackupService(zip codec); addWhereController.exportBackup()/importBackup(from:strategy:)(singleperformtransaction, security-scoped URL bracketing).WhereModelbridges export/import with aBackupState+backupError;SettingsViewadds Export (share sheet + temp cleanup) and Import (fileImporter-> merge/replaceconfirmationDialog-> success/error alert), both showing progress while in flight. Newsettings.backup.*string-catalog entries.Dependency
WhereCoretarget (Foundation has no public zip reader). Committed as standalone groundwork, matching the existingswift-snapshot-testingexternal-SPM pattern.Notes
formatVersion) so future schema changes can refuse/migrate.Test plan
tuist test WhereCoreTests— manifest + zip round-trip, non-zip rejection (BackupServiceTests); controller merge/replace round-trips, merge keeps pre-existing rows, replace wipes them,clearAllempties all tables (WhereControllerTests).tuist test WhereUITests— model two-store round-trip + bogus-file failure (WhereModelBackupTests); backup string resolution + 3-arg import-summary ordering (StringsTests);SettingsViewhosts the new section crash-free (ScreenHostingTests)../swiftformat --lintclean.Made with Cursor