Rebuild the last two non-Form screens, and drop the folder picker wrapper - #556
Merged
Merged
Conversation
bradleymackey
force-pushed
the
native-standardization-feed-toolbar
branch
from
September 14, 2026 16:49
4b4f990 to
b581376
Compare
Base automatically changed from
native-standardization-feed-toolbar
to
main
September 14, 2026 16:49
…pper `OpenSourceView` and `VaultAutofillConfigurationView` were the only two screens in the app with no `Form` or `List` anywhere. Both faked inset-grouped styling by hand. ## OpenSourceView A `GeometryReader` + `ScrollView` + `VStack` with `.frame(minHeight: geometry.size.height)` to fake vertical centring, and a bare `Link` styled with `.foregroundStyle(.tint)`. Now a `Form` matching `VaultAboutView`: a `PlaceholderView` header section, a section holding the two paragraphs, and the GitHub link as a `FormRow` row. The row uses the same purple glyph tile that `VaultAboutView` already uses for its Open Source entry, so the two screens agree. ## VaultAutofillConfigurationView A `ScrollView` + centred `VStack` + `Spacer()`s + a double `containerRelativeFrame`, with two hand-drawn `RoundedRectangle(cornerRadius: 12).fill(.secondarySystemGroupedBackground)` "cards" imitating inset-grouped list rows. Now a real `List`: the hero is a section, the two features are ordinary `Label` rows (so they get the system's row separator and alignment), and the Continue button moves into `.safeAreaInset(edge: .bottom)`. Fixed sizes on text are gone with it — `.system(size: 28, weight: .bold)` on the title becomes `.title.bold()`, and the supporting line becomes `.headline`. Both scale with Dynamic Type now. The 64pt header glyph stays fixed, since it is decorative, but takes `.tint` instead of a literal `.blue`. ## FolderPickerView `AutoBackupSettingsView` presented a `.sheet` containing a `UIViewControllerRepresentable` around `UIDocumentPickerViewController`, with its own `Coordinator` and delegate, purely to pick a folder. `.fileImporter(isPresented:allowedContentTypes: [.folder])` does this natively, and `BackupImportFlowView` already uses `.fileImporter` elsewhere. The wrapper and its coordinator are deleted (~30 lines). Both APIs vend a security-scoped URL and `configureSelectedProvider(with:)` is unchanged, so the handling either side of the picker is the same. A cancelled pick is ignored rather than surfaced, matching the old delegate, which only forwarded on `didPickDocumentsAt`. ## Verification Local, iPhone 18 Pro Max / iOS 27.0: - `xcodebuild build-for-testing` — `TEST BUILD SUCCEEDED` - Full suite, `-parallel-testing-enabled NO` — `TEST EXECUTE SUCCEEDED`, 2834 passed, 0 failures across 24 bundles - 2 snapshots re-recorded (`OpenSourceView`, `VaultAutofillConfigurationView`) and visually reviewed - `make format` + `make lint` — clean⚠️ The folder-picker change is **not** covered by an automated test — a file importer cannot be driven from a snapshot test. It needs a manual run: pick a folder, confirm the security-scoped bookmark still resolves and that auto-backup writes to it.⚠️ Automatic CI is still disabled (#548), so this is local verification only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bradleymackey
force-pushed
the
native-standardization-forms
branch
from
September 14, 2026 16:50
9c52731 to
4c73962
Compare
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.
OpenSourceViewandVaultAutofillConfigurationViewwere the only twoscreens in the app with no
FormorListanywhere. Both fakedinset-grouped styling by hand.
OpenSourceView
A
GeometryReader+ScrollView+VStackwith.frame(minHeight: geometry.size.height)to fake vertical centring, anda bare
Linkstyled with.foregroundStyle(.tint).Now a
FormmatchingVaultAboutView: aPlaceholderViewheadersection, a section holding the two paragraphs, and the GitHub link as a
FormRowrow. The row uses the same purple glyph tile thatVaultAboutViewalready uses for its Open Source entry, so the twoscreens agree.
VaultAutofillConfigurationView
A
ScrollView+ centredVStack+Spacer()s + a doublecontainerRelativeFrame, with two hand-drawnRoundedRectangle(cornerRadius: 12).fill(.secondarySystemGroupedBackground)"cards" imitating inset-grouped list rows.
Now a real
List: the hero is a section, the two features are ordinaryLabelrows (so they get the system's row separator and alignment), andthe Continue button moves into
.safeAreaInset(edge: .bottom).Fixed sizes on text are gone with it —
.system(size: 28, weight: .bold)on the title becomes
.title.bold(), and the supporting line becomes.headline. Both scale with Dynamic Type now. The 64pt header glyphstays fixed, since it is decorative, but takes
.tintinstead of aliteral
.blue.FolderPickerView
AutoBackupSettingsViewpresented a.sheetcontaining aUIViewControllerRepresentablearoundUIDocumentPickerViewController,with its own
Coordinatorand delegate, purely to pick a folder..fileImporter(isPresented:allowedContentTypes: [.folder])does thisnatively, and
BackupImportFlowViewalready uses.fileImporterelsewhere. The wrapper and its coordinator are deleted (~30 lines).
Both APIs vend a security-scoped URL and
configureSelectedProvider(with:)is unchanged, so the handling either side of the picker is the same. A
cancelled pick is ignored rather than surfaced, matching the old delegate,
which only forwarded on
didPickDocumentsAt.Verification
Local, iPhone 18 Pro Max / iOS 27.0:
xcodebuild build-for-testing—TEST BUILD SUCCEEDED-parallel-testing-enabled NO—TEST EXECUTE SUCCEEDED,2834 passed, 0 failures across 24 bundles
OpenSourceView,VaultAutofillConfigurationView) and visually reviewedmake format+make lint— cleanfile importer cannot be driven from a snapshot test. It needs a manual
run: pick a folder, confirm the security-scoped bookmark still resolves
and that auto-backup writes to it.
🤖 Generated with Claude Code