refactor: delete legacy migration code from AppState (Stage 2) - #32
Merged
Conversation
Stage 2 of the AppState god-object split. Deletes the build-31 → multi-account migration code that's no longer needed: App Store Connect data shows 4/40 users on legacy versions, none active. The worst case for those inactive users is OnboardingView + nsec re-import on next launch, equivalent to a normal reinstall — and given the build 31 → 64 gap, their proxy-side state has likely already decayed beyond what migration could preserve. Deleted from AppState.swift (-330 LOC): - bootstrapFromLegacyKeychainIfNeeded - legacyCachedProfile - migrateLegacyUserDefaultsKeys - migrateLegacyProfileImageFile - migrateRemainingLegacyKeysIfNeeded - cleanupEmptySignerRowsIfSafe (build-32 regression backfill, also dead) - backfillSignerPubkeyHex - 3 call sites in loadState() + their step comments - "// MARK: - Multi-account loading + bootstrap (Task 5)" header - Stale doc-comment reference in cachedImageURL Deleted from ClaveTests/AppStateMultiAccountTests.swift (-200 LOC): - testBootstrap_legacyKeychainEntryPresent_seedsAccountsKey - testBootstrap_freshInstall_noLegacyState_isNoOp - testBootstrap_backfillsSignerPubkeyHexOnLegacyRecords - testBootstrap_migratesLegacyCachedProfile_intoAccountProfile - testBootstrap_migratesLegacyBunkerSecret_intoPerSignerDict - testBootstrap_migratesLegacyLastContactSet_intoPerSignerDict - testBootstrap_migratesLegacyRegisterTimestamps - testMigrateRemainingLegacyKeys_idempotentlyCleansUpAfterPriorBootstrap - 3 stale MARK section headers - Class doc comment updated to reflect the new surface Kept: - recoverAccountsFromKeychainIfNeeded — NOT legacy migration code. Handles the Apple-documented "iOS Storage settings UserDefaults wipe" scenario where Keychain persists but UserDefaults is empty (e.g., user deletes "App Data" via iOS Settings → Storage). - cleanupOrphanLegacyKeychainEntry — sunset candidate, kept for one more cycle as a defensive sweep for any pre-existing build-31-era bootstrap orphan that was never cleaned. Doc updated to flag this. - testReinstallRecovery_seedsAccountsFromKeychain (×2) - testCleanupOrphanLegacyKeychainEntry_idempotent Verification: - xcodebuild test -skip-testing:ClaveUITests on iPhone 17 / iOS 26.4: - Pre-baseline (main @ b4c1b72): 240 passed / 0 failed - Post-deletion (this branch): 232 passed / 0 failed (240 - 8) - ** TEST SUCCEEDED ** in both runs, identical surviving tests pass AppState.swift: 2,261 -> 1,934 LOC (-327, -14.5%). Total diff: -540 / +16 across two files. Stages 3-4 of the AppState split (ProfileFetcher, NostrConnectCoordinator, ProxyClient, AccountManager, PendingApprovalCoordinator) remain in BACKLOG; each gets its own PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three doc/comment blocks referenced the now-deleted Task 8 migration: - loadCachedProfileImage doc said 'Task 8 will move' the legacy per-pubkey image filename — but the cachedImageURL computed property already returns the per-pubkey path. Stale present-tense aspiration about deleted code. - deleteAccount step 6 sweep called the legacy global 'profile_image.jpg' cleanup 'meaningful only for users who never went through Task 8 migration' — reframe as defensive build-31-era orphan sweep since there's no migration path now. - An archaeological comment block above renamePetname referenced 'Task 8 will explicitly remove' the legacy cachedProfileKey — also stale; trimmed to just describe current state. Comment-only changes; no executable code touched. xcodebuild test result unchanged (232/232). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
DocNR added a commit
that referenced
this pull request
May 7, 2026
…light (#33) Internal-only build for on-device verification of the AppState legacy migration deletion (#32). Bumps CURRENT_PROJECT_VERSION 64 -> 65 across all 4 targets (Clave, ClaveTests, ClaveUITests, ClaveNSE) in both Debug and Release configs. Stage 2 verification gates: - xcodebuild test passes pre/post (240/240 -> 232/232; 8 deleted tests covered the deleted migration paths) ✓ - Build 65 archives + uploads to TestFlight (this PR enables) - Multi-account flow exercises live loadState() path on device: fresh install, addAccount, switchAccount, deleteAccount, scenePhase .active re-register, nostrconnect pair + sign Stages 3-4 of the AppState god-object split remain in BACKLOG. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
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 freeto 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
Stage 2 of the AppState god-object split. Deletes the build-31 → multi-account migration code that's no longer needed.
The data: App Store Connect shows 4/40 users on legacy versions, none active. The worst case for those inactive users is
OnboardingView+ nsec re-import on next launch — equivalent to a normal reinstall. Given the build 31 → 64 gap (33+ builds, weeks/months), their proxy-side state has likely already decayed beyond what migration could preserve.The original BACKLOG entry ("move as-is, do NOT delete") was based on the assumption that legacy users were active. The data refutes that. Cleaner to delete than to extract dead code into a separate file with a TODO-delete comment.
Numbers
Clave/AppState.swiftClaveTests/AppStateMultiAccountTests.swiftAppState.swiftis −14.5% in this PR alone.What was deleted
From
Clave/AppState.swift:bootstrapFromLegacyKeychainIfNeeded(one-shot build-31 → multi-account)legacyCachedProfile(helper for bootstrap)migrateLegacyUserDefaultsKeys(UserDefaults shape change)migrateLegacyProfileImageFile(disk filename change)migrateRemainingLegacyKeysIfNeeded(cross-version upgrade catch-up)cleanupEmptySignerRowsIfSafe(build-32 regression backfill, also dead — build 32 was 30+ builds ago)backfillSignerPubkeyHex(helper for the above)loadState()+ their step comments// MARK: - Multi-account loading + bootstrap (Task 5)headercachedImageURLto a deleted methodFrom
ClaveTests/AppStateMultiAccountTests.swift:loadState()→ assert migrated)What was kept
recoverAccountsFromKeychainIfNeeded— NOT legacy migration code. Handles the Apple-documented "iOS Storage settings UserDefaults wipe" scenario where Keychain persists but UserDefaults is empty (e.g., user clears "App Data" via iOS Settings → Storage).cleanupOrphanLegacyKeychainEntry— sunset candidate. Kept for one more cycle as a defensive sweep for any pre-existing build-31-era bootstrap orphan that was never cleaned. Doc-comment updated to flag this. Safe to delete in a few build cycles.Test plan
xcodebuild test -scheme Clave -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.4' -skip-testing:ClaveUITestsonmain(pre-baseline): 240 passed / 0 failed ✅** TEST SUCCEEDED **in both runs.swiftfile (grep -rnE "bootstrapFromLegacy|migrateLegacy*|backfillSignerPubkeyHex|legacyCachedProfile|cleanupEmptySigner" --include="*.swift"→ empty)chore/pbxproj-build-65PR bumpsCURRENT_PROJECT_VERSION64 → 65; archive + on-device verify (fresh install + multi-account flow exercises liveloadState()path)Worst-case impact for the 4 inactive legacy users
If any of them auto-updates and opens the app:
OnboardingViewThis is the same UX as any normal reinstall scenario. Given the build 31 → 64 gap, their server-side proxy state was likely going to need re-pairing anyway.
Stages 3-4
ProfileFetcher,NostrConnectCoordinator,ProxyClient,AccountManager,PendingApprovalCoordinatorextractions remain in BACKLOG. Each gets its own brainstorm + plan + PR cycle.🤖 Generated with Claude Code