refactor: extract ProxyClient into AppState extension (Stage 3c) - #39
Merged
Conversation
Stage 3c — last of the Stage 3 sub-extractions. Moves all proxy HTTP + NIP-98 + retry-queue logic out of AppState.swift into a dedicated extension file. The new file owns the [Pair] log category from PR #27. Moves out of Clave/AppState.swift: - registerWithProxy(completion:) — single-account wrapper - registerAllAccountsWithProxy() — multi-account fan-out - registerSignerWithProxy(signer:completion:) — private NIP-98 POST /register impl - ensureRegisteredFresh() — throttled scenePhase trigger - ensureAllRegisteredFresh() — multi-account variant - unregisterWithProxy(signer:) — NIP-98 POST /unregister - pairClientWithProxy(clientPubkey:relayUrls:signer:) — POST /pair-client with retry queue on failure - unpairClientWithProxy(clientPubkey:signer:) — POST /unpair-client - drainPendingPairOps() — retry queue worker - retryPairOp(op:relayUrls:) — private retry impl - retryUnpairOp(op:) — private retry impl - "// MARK: - Proxy per-client-relay (V2)" header - private let logger = Logger(...category: "pair") relocated from AppState.swift line 8 (16/16 of its uses moved with the cluster) Into the new file: - Clave/AppState+ProxyClient.swift (577 LOC) — extension AppState Logger relocation note: declaring Logger(subsystem:..., category:...) in two files is equivalent — multiple Logger instances with the same subsystem + category combine into one log stream. The dev-menu "Copy Recent Logs" path (LogExporter.allCategories includes "pair") is unchanged. Zero behavior change: - Function bodies preserved byte-for-byte. - Original `private` modifiers preserved on registerSignerWithProxy, retryPairOp, retryUnpairOp. - All call sites (network code, Task { @mainactor in } closures, URLSession callbacks, NIP-98 signing) unchanged. - Cross-extension call from AppState+NostrConnect.swift's handleNostrConnect to pairClientWithProxy still works — both are extension AppState in the same module. External callers unchanged: - Clave/ClaveApp.swift (drainPendingPairOps via NotificationCenter) - Clave/Views/MainTabView.swift (ensureRegisteredFresh + ensureAllRegisteredFresh on scenePhase) - Clave/Views/Settings/SettingsView.swift (registerWithProxy, unregisterWithProxy) - Clave/Views/Onboarding/OnboardingView.swift (registerWithProxy) - Clave/Views/Home/HomeView.swift (registerAllAccountsWithProxy) - Clave/Views/Home/ClientDetailView.swift (unpairClientWithProxy) All call via appState.<method> — extension dispatch. No pbxproj edits — Clave/ directory is auto-synced. Verification: - xcodebuild test -skip-testing:ClaveUITests on iPhone 17 / iOS 26.4: - Pre-baseline (main @ 11c7117): 232 passed / 0 failed - Post-extraction (this branch): 232 passed / 0 failed - ** TEST SUCCEEDED ** in both runs AppState.swift: 1,483 -> 922 LOC (-561, -37.8%). Combined Stages 1+2+3a+3b+3c: 2,338 -> 922 LOC (-60.6% from original). Stage 4 (AccountManager + PendingApprovalCoordinator, ~700 LOC) is the final and highest-risk extraction — touches multi-account @observable surface and the alert-chain state machine. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
DocNR added a commit
that referenced
this pull request
May 8, 2026
Internal-only build for on-device verification of [#39] (the final Stage 3 sub-extraction; -561 LOC from AppState.swift, [Pair] logger relocated). Bumps CURRENT_PROJECT_VERSION 67 -> 68 across all 4 targets in both Debug and Release configs. Stage 3c verification gates (all in one TF cycle): - xcodebuild test passes pre/post (232/232; no test count change) ✓ - Build 68 archives + uploads to TestFlight (this PR enables) - Live network paths exercised on device: - Cold launch -> ensureRegisteredFresh fires - Pair a fresh client -> pairClientWithProxy registers URI relays with proxy + emits [Pair] log breadcrumbs - Sign one event end-to-end (proxy catches request -> APNs -> NSE -> response published) - Settings -> Register button (registerWithProxy) - Delete an account -> unregisterWithProxy + unpairClientWithProxy fire on the deleted account's pubkey Stage 3 is now complete: - Stage 1: RelayUtils.swift (pure helpers) - Stage 2: legacy migration deletion - Stage 3a: AppState+NostrConnect.swift - Stage 3b: AppState+ProfileFetcher.swift - Stage 3c: AppState+ProxyClient.swift (this) AppState.swift: 2,338 -> 922 LOC (-60.6% from sprint start). Stage 4 (AccountManager + PendingApprovalCoordinator, ~700 LOC) is the final extraction. 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 3c — last of the Stage 3 sub-extractions. Moves all proxy HTTP + NIP-98 + retry-queue logic out of
AppState.swiftinto a dedicated extension file. The new file owns the[Pair]log category from PR #27.Numbers
Clave/AppState.swiftClave/AppState+ProxyClient.swiftCombined Stages 1+2+3a+3b+3c: AppState.swift down from 2,338 → 922 LOC (−60.6%). Within reach of the BACKLOG-sketched ~300 LOC slim AppState target after Stage 4.
What moved
Into Clave/AppState+ProxyClient.swift:
registerWithProxy(completion:)registerAllAccountsWithProxy()registerSignerWithProxy(signer:completion:)privateensureRegisteredFresh()ensureAllRegisteredFresh()unregisterWithProxy(signer:)pairClientWithProxy(...)unpairClientWithProxy(...)drainPendingPairOps()retryPairOp(op:relayUrls:)privateretryUnpairOp(op:)private// MARK: - Proxy per-client-relay (V2)private let logger = Logger(...category: "pair")Logger relocation
The file-scope
private let logger = Logger(subsystem: "dev.nostr.clave", category: "pair")(line 8 of AppState.swift) is relocated to the top of the new file. All 16 of its uses moved with the cluster — AppState.swift had zero remaining users after the extraction.Multiple
Loggerinstances with the same subsystem+category are equivalent — log streams combine. Dev-menu "Copy Recent Logs" viaLogExporter.allCategories(which includes"pair") is unchanged.Zero behavior change
privatemodifiers preserved onregisterSignerWithProxy,retryPairOp,retryUnpairOpTask { @MainActor in }closures, URLSession callbacks, NIP-98 signing paths unchangedAppState+NostrConnect.swift'shandleNostrConnecttopairClientWithProxystill resolves correctly (both areextension AppStatein the same module)External callers unchanged
Clave/ClaveApp.swiftdrainPendingPairOps(NotificationCenter observer)Clave/Views/MainTabView.swiftensureRegisteredFresh,ensureAllRegisteredFreshClave/Views/Settings/SettingsView.swiftregisterWithProxy,unregisterWithProxyClave/Views/Onboarding/OnboardingView.swiftregisterWithProxyClave/Views/Home/HomeView.swiftregisterAllAccountsWithProxyClave/Views/Home/ClientDetailView.swiftunpairClientWithProxyAll call via
appState.<method>— extension dispatch.Test plan
xcodebuild test -skip-testing:ClaveUITestson iPhone 17 / iOS 26.4 againstmain(pre-baseline): 232 passed / 0 failed ✅** TEST SUCCEEDED **in both runs (no test count change)chore/pbxproj-build-68PR; archive build 68 + on-device verify of the proxy + NIP-46 hot path:ensureRegisteredFreshfires (look for[Pair] pair-client begin/okin logs if relevant)pairClientWithProxyregisters URI relays with proxyunregisterWithProxy+unpairClientWithProxyfire on the deleted account's pubkeyStage 4
Final stage:
AccountManager+PendingApprovalCoordinator(~700 LOC). Highest-risk because it touches the multi-account@Observablesurface and the alert-chain state machine. After it lands, AppState.swift should be at the ~300-LOC slim target.🤖 Generated with Claude Code