refactor: extract NostrConnect handlers into AppState extension (Stage 3a)#34
Merged
Conversation
…e 3a) Stage 3a of the AppState god-object split — the smallest, lowest-risk of the three Stage 3 sub-extractions (NostrConnect, ProfileFetcher, ProxyClient). Establishes the extension-with-mutating-state pattern for the remaining Stage 3 work. Moves out of Clave/AppState.swift: - handleDeeplink(url:) — DeeplinkRouter wiring + dispatch to pendingNostrconnectURI vs pendingDeeplinkAccountChoice based on account count - handleNostrConnect(parsedURI:permissions:boundAccountPubkey:) — the full multi-relay nostrconnect handshake (~143 LOC): parallel connect, publish-with-retry, listen, dispatch RPCs to LightSigner, fire pairClientWithProxy on first success - "// MARK: - Deeplink handling (Task 3.3)" header Into the new file: - Clave/AppState+NostrConnect.swift (180 LOC) — extension AppState What stays in AppState.swift: - The 3 @observable stored properties (pendingNostrconnectURI, pendingDeeplinkAccountChoice, deeplinkBoundAccount). Swift forbids stored properties in extensions. Pointer comment added so readers can find the handlers. - The NotificationCenter observer in AppState.init that calls self.handleDeeplink(url:) — works fine as an extension method call. Zero behavior change: - Function bodies preserved byte-for-byte (no logic edits, only file relocation). - @mainactor on handleDeeplink preserved. - All Shared/ dependencies (RelayUtils from Stage 1, LightSigner, LightCrypto, LightEvent, SharedKeychain, SharedStorage, etc.) work unchanged from the extension. - pairClientWithProxy(...) call inside handleNostrConnect resolves to the extension's `self` (still on AppState until Stage 3c). External callers unchanged: - ClaveApp.swift posts .deeplinkReceived to NotificationCenter; the observer in AppState.init dispatches to handleDeeplink. - HomeView.swift and ConnectSheet.swift call appState.handleNostrConnect directly — works as an extension method on AppState. No pbxproj edits — Clave/ directory is a PBXFileSystemSynchronizedRootGroup, so the new file is picked up automatically by the Clave target. Verification: - xcodebuild test -skip-testing:ClaveUITests on iPhone 17 / iOS 26.4: - Pre-baseline (main @ 81576e7): 232 passed / 0 failed - Post-extraction (this branch): 232 passed / 0 failed - ** TEST SUCCEEDED ** in both runs AppState.swift: 1,934 -> 1,770 LOC (-164, -8.5%). Combined Stages 1+2+3a: 2,338 -> 1,770 LOC (-24.3% from original). Stage 3b (ProfileFetcher) and 3c (ProxyClient) follow. 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
…ght (#35) Internal-only build for on-device verification of the AppState NostrConnect handler extraction (#34). Bumps CURRENT_PROJECT_VERSION 65 -> 66 across all 4 targets (Clave, ClaveTests, ClaveUITests, ClaveNSE) in both Debug and Release configs. Stage 3a verification gates: - xcodebuild test passes pre/post (232/232; no test count change) ✓ - Build 66 archives + uploads to TestFlight (this PR enables) - nostrconnect pair + sign on device exercises the moved handlers: - handleDeeplink (Universal Link / URL scheme entry) - handleNostrConnect (multi-relay handshake) Stages 3b (ProfileFetcher) and 3c (ProxyClient) follow. 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 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
Stage 3a of the AppState god-object split. Smallest of the three Stage 3 sub-extractions (NostrConnect, ProfileFetcher, ProxyClient) — establishes the extension-with-mutating-state pattern that Stages 3b and 3c will reuse.
Numbers
Clave/AppState.swiftClave/AppState+NostrConnect.swiftCombined Stages 1+2+3a: AppState.swift down from 2,338 → 1,770 LOC (−24.3%).
What moved
Into Clave/AppState+NostrConnect.swift:
handleDeeplink(url:)—DeeplinkRouterwiring + dispatchhandleNostrConnect(parsedURI:permissions:boundAccountPubkey:)— the full multi-relay handshake (~143 LOC)// MARK: - Deeplink handling (Task 3.3)section header (now redundant in AppState.swift)What stays in AppState.swift:
@Observablestored properties (pendingNostrconnectURI,pendingDeeplinkAccountChoice,deeplinkBoundAccount). Swift forbids stored properties in extensions, so they remain on the class. Pointer comment added so readers can find the handlers.NotificationCenterobserver inAppState.initthat callsself.handleDeeplink(url:)— works fine as an extension method call.Design decisions (carried over from Stage 2)
extension AppState(matches Stage 2 LegacyMigrations pattern, before deletion)Clave/AppState+NostrConnect.swift(matchesSharedKeychain+Enumeration.swiftprecedent)internal(no change)Why no pbxproj edits
Clave/is aPBXFileSystemSynchronizedRootGroup— the new file is picked up automatically by the Clave target. (Compare to Stage 1 whereShared/is a regularPBXGroupand required 4 explicit pbxproj entries forRelayUtils.swift.)Zero behavior change
@MainActoronhandleDeeplinkpreservedpairClientWithProxy(...)call insidehandleNostrConnectresolves toself(still on AppState until Stage 3c moves it; the cross-extension call will keep working since both will beextension AppState)External callers (ClaveApp.swift, HomeView.swift, ConnectSheet.swift) unchanged — they call
appState.handleNostrConnect(...)which resolves to the extension method.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 — no tests directly exercise these methods; integration is verified via TF)chore/pbxproj-build-66PR; archive build 66 + on-device verify nostrconnect pair flow with control client (e.g. Jumble / noStrudel)Stages 3b + 3c
ProfileFetcher(~300 LOC) andProxyClient(~400 LOC) follow as separate PRs after this verifies clean.🤖 Generated with Claude Code