Snapshot the rebuilt backup and encryption-edit screens - #573
Merged
Merged
Conversation
BackupKeyDecryptorView (including a real driven decrypt-failure state), BackupImportFlowView in all three import contexts, and VaultDetailEncryptionEditView had no view coverage since the SwiftUI rebuild. AutoBackupSettingsView stays covered transitively through BackupCreateView; its async-driven states need an initial-state injection refactor to snapshot without flake. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bradleymackey
added a commit
that referenced
this pull request
Sep 15, 2026
## Summary
The backup surface was split across two sidebar peers ("Backup" and
"Restore"), with the Backup screen stacking four unrelated jobs —
password management, inline auto-backup settings, PDF export, and device
transfer — into one form. The password screen's "Generate Key" action
also read as password generation when it actually derives an encryption
key from a typed password. This PR restructures the IA:
- **Single "Backups" sidebar item** opening a hub: a last-backup status
banner (revives the orphaned `LastBackupSummaryView`) plus rows for
Auto-Backup, Export, Restore, and Backup Password. `BackupCreateView`
becomes `BackupExportView` (PDF + device transfer only); its
strings-only view model and nine orphaned xcstrings keys are deleted.
- **Backup password screen reframed**: the action is "Set Backup
Password" (revealed alongside the entry fields once a password is
typed), key derivation is presented as a progress state with distinct
cancelled/error copy, and the About text explains derivation without the
"generate" framing. Keygen cancellation and plaintext clearing are
untouched.
- **Auto-Backup gets its own screen** backed by a new
`AutoBackupViewModel` (the initial-state-injection refactor deferred in
#573): state is seeded synchronously from the service, provider states
are injectable for tests, the destination row shows provider name +
folder with a "Change" affordance, folder-configure failures surface as
an error row with a recovery suggestion (previously swallowed by an
empty `catch`), and the active provider resolves from configuration
instead of a hardcoded `availableProviders.first`.
## Manifesto review
Reviewed against `MANIFESTO.md`, corollary by corollary, since backups
are explicitly governed by it. This is a navigation and copy
restructure: backup payloads, encryption, and every action's mechanics
are untouched. **C1** — no bulk operation added; nothing touches
per-item safety fields. **C2/C3** — no new logging, telemetry, or error
channels near duress features; the one new error surface (auto-backup
folder configuration failures, previously swallowed by an empty `catch`)
reports storage-provider errors only. **C4** — device authentication
still gates every operation that loads or uses the backup key: export,
password set/change, the auto-backup screen, and restore imports all
authenticate exactly as before; the new un-gated hub adds navigation,
not capability. **C5** — the hub banner shows backup recency and kind,
never contents; no enumeration of protected items. **C6** — no undo, no
audit surface. **C7** — no protective default flipped; auto-backup
remains opt-in and its configuration remains behind device auth. **C8**
— no sensitive operation lost a step: merging the sidebar items removes
a hop between two navigation screens, while every export, restore, and
password flow retains its full sequence including authentication; the
password screen reframe changes verbs ("Generate Key" → "Set Backup
Password"), not gates. **C9** — backup surfaces never referenced duress
features; unchanged. **C10** — backup, export, and transfer payloads are
byte-identical. One visibility change flagged deliberately: the
last-backup banner (date + kind) is now visible without device auth,
where the old Backup screen showed nothing pre-auth. Weighed against
C4/C7: the same fact is already discoverable without auth (the Restore
screen is un-gated today; auto-backup files are visible in the Files
app), the banner reveals neither destination, contents, nor
protected-item structure, and surfacing backup staleness is itself a
data-loss protection.
## Testing
- Full `iOSAllTests` plan passes locally on iPhone 18 Pro Max / iOS
27.0.
- New suites: `AutoBackupViewModelTests` (19 tests),
`AutoBackupViewSnapshotTests` (7 scenarios — the coverage #573
deferred), `LastBackupSummaryViewSnapshotTests` (4, with an injectable
`now` for deterministic staleness).
- Re-recorded and visually reviewed: `BackupKeyChangeViewSnapshotTests`,
`BackupViewSnapshotTests` (hub + export),
`VaultMainNavigationViewSnapshotTests`.
- Simulator walk-through of the full flow passed: single sidebar entry,
hub layout, every push/back/sheet, and every auth gate failing closed.
- Not automatable (per #556): a real folder pick through `.fileImporter`
— needs one manual run with iCloud Drive.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <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.
Changes (test-only) — final PR of the pre-release audit series (#561–#572)
Snapshot coverage for security-relevant screens rebuilt in #551–#560 that had no view tests:
BackupKeyDecryptorViewSnapshotTests— initial state (light/dark × 3 type sizes, in a NavigationStack so the Cancel toolbar renders) plus a deterministic decrypt-failure state: the failure is produced by actually runningattemptDecryption()with the fast testing deriver and an erroring decoder mock, not by faking view state.BackupImportFlowViewSnapshotTests— all threeBackupImportContextvariants (empty vault / merge / override), light/dark.VaultDetailEncryptionEditViewSnapshotTests— encryption-disabled (full grid) and encryption-enabled variants.SettingsDangerViewwas covered in Cover the Danger Zone view model and add its first snapshots #565;AutoBackupSettingsViewis deliberately not given a standalone suite — it is already snapshotted transitively throughBackupCreateView(BackupViewSnapshotTests), and its enabled/error states are only reachable through an async.taskhandoff that would flake under synchronous snapshot rendering. Driving those states needs a small initial-state injection refactor — left as follow-up.Release gate: full
CI_iOSscheme (all 13 test targets,iOSAllTestsplan including the TSAN configuration) run locally on iPhone 18 Pro Max / iOS 27.0.Release findings — report-only (no code in this series)
The pre-release audit surfaced the following items that need design decisions, not patches. Recorded here so they are not lost:
MANIFESTO C7 gaps (protective defaults):
PasteTTL.default = nil) — copied OTPs/passwords sit on the pasteboard indefinitely unless the user opts in to a TTL.privacySensitive(), no capture detection, no cover view).Design-level:
.onlyPassphraseitems (rows exist, digests unverifiable).payloadHashandlastBackupHashlive in plaintext UserDefaults — mutation-time evidence (C6 tension).deleteVault()does not refresh the auto-backup hash, so the newest auto-backup still describes the wiped vault (recovery safety net vs C6 — decide).DerivedEncryptionKey.debugDescriptionprints raw key material as hex; keychain replace (remove→store) is non-atomic; killphrase/passphrase edit fields are plainTextFieldnotSecureField; thevault://HOTP-increment deep link is unauthenticated;Data.randomrelies onSystemRandomNumberGenerator(CSPRNG on Apple platforms, but unannotated as the app's sole randomness source); noprotectedDataWillBecomeUnavailablehandling.Hygiene (non-blocking): CI triggers commented out; CHANGELOG ~9 versions stale vs MARKETING_VERSION 2.0; hardcoded strings in rebuilt screens bypass the string catalogs (app is currently English-only, so cosmetic); stale scheme/test-plan references (
CI_iOSscheme, orphanVaultUITestsscheme) and a stale snapshot directory;VaultBackup.xcstringsnot declared as a target resource; the keygen speedtest CLI prints a derived key in hex; feed search reload has no debounce/cancellation;ForEachidentity built fromHasher().finalize(); reorder persist failures are swallowed.🤖 Generated with Claude Code