fix(signer): switch_relays returns null (NIP-46 responder-only)#7
Merged
Conversation
7 tasks
Matches Amber and NDK default. Previously returned ["wss://relay.powr.build"], which triggered welshman pool migration in Coracle's NIP-46 client and stalled the pairing UI indefinitely. Bunker flow unaffected (switch_relays is only emitted during nostrconnect handshakes). Validated end-to-end on TestFlight builds 16+17 during the 2026-04-19 proxy-per-client-relay diagnostic. Unit test locks the wire format.
The 120s .expirationDate only fires on iPhone; Universal Clipboard does not propagate expiration metadata to Mac-side NSPasteboard. Residual exposure on the Mac is bounded by SharedStorage.rotateBunkerSecret() firing on successful pair, which makes stale clipboard copies dead. No behavior change — comment-only clarification after build 20 device-testing revealed the earlier comment's promise of 'auto-clear after 2 min' was ambiguous about which device that applies to.
fa648f2 to
4c36850
Compare
This was referenced Apr 20, 2026
DocNR
added a commit
that referenced
this pull request
Apr 21, 2026
* proxy: embed caught event in APNs push payload (≤3500B) Avoids the NSE ephemeral-fetch race when the relay drops kind:24133 between proxy dispatch and NSE wake. Oversized events fall through to the existing fetch path (no regression). Part of build 22 response-delivery hotfix. See ~/hq/clave/specs/2026-04-20-response-delivery-fixes-design.md * ios: prefer embedded event from push payload over relay fetch NSE and AppDelegate foreground-push handler now check userInfo["event"] first; fall through to LightRelay.fetchEvents only if absent. Closes the ephemeral-fetch race for ≤3500B events. Oversized events fall through as today. Part of build 22 response-delivery hotfix. * ios: distinguish cast-miss from key-missing in embed fallback log Spec Risk #1 mitigation: when userInfo["event"] is present but not a dict, log a warning so proxy payload-shape regressions are visible in production triage. Unchanged: behavior on happy path + missing-key path. * ios: thread relay URL through PendingRequest approve-later flow PendingRequest gains relayUrl: String? captured at queue-time from the incoming responseRelayUrl. approvePendingRequest threads it back into LightSigner.handleRequest, so signed responses for protected kinds publish to the relay the client is actually subscribed on — not the powr.build fallback. Bug was latent since PR #7 (switch_relays → null) — nostr-tools clients stopped migrating to powr.build, so the fallback stopped matching. Bunker flow unaffected because Clave's bunker URIs are already pinned to powr.build. Codable's default synthesis handles missing keys on Optional fields, so pre-build-22 pending rows decode as nil → fall back to powr.build (unchanged broken behavior for pre-upgrade stragglers). Part of build 22 response-delivery hotfix. * ios: rename PendingRequest.relayUrl to responseRelayUrl Matches the parameter name on LightSigner.handleRequest so the call site reads `responseRelayUrl: request.responseRelayUrl` instead of `responseRelayUrl: request.relayUrl` — self-documenting round trip. Also corrects "forward" → "backward" compatibility in the field's doc comment (new code reading pre-upgrade rows is backward compat, not forward).
4 tasks
DocNR
added a commit
that referenced
this pull request
May 2, 2026
Brainstorm review of design-system.md against shipped code surfaced 9 inconsistencies + 1 anti-pattern still present. Fixed everything in one batch so the next TestFlight archive carries it all. Code: - HomeView: drop .padding(.bottom, 8) on SlimIdentityBar invocation — slim banner owns its outer bottom padding (12pt); stacking another 8pt on top was double-counting (review #4) - HomeView: drop .padding(.bottom, 8) inside statsRow — listSectionSpacing(0) carries the gap to Connected Clients; the residual padding kept the visible gap excessive after polish round 2 (review #2) - AccountDetailView: avatarLarge letter fallback opacity 0.25 → 0.22 to align with SlimIdentityBar's 0.22 (review #3) - ConnectSheet: add .presentationBackground(Color(.systemGroupedBackground)) — was the last sheet still defaulting to translucent (review #9) - ApprovalSheet: rename @State capExceeded → showConnectionCapAlert for naming convention parity with HomeView (review #7) design-system.md: - New "Cross-platform applicability" section at the top — clarifies what carries directly to clave.casa web companion (color tokens, displayLabel rule, identity-vs-functional zone philosophy, avatar treatments, copy patterns, anti-patterns) vs what's iOS-only (SwiftUI modifiers, haptics, sheet/toolbar conventions) - §3 Typography: corrected initial-letter font scale — AvatarView uses size*0.35 mono (pubkey) or size*0.4 proportional (name); was wrongly documented as a single 0.37 (review #1) - §4 Avatars: added Treatment Selection Rule table (B on neutral bg, C on saturated theme gradient) + clarified 1-vs-2 letter behavior (review #5, #6) - §4 Sizing scale: expanded table to include initial font + border thickness per slot, with the ~5% border scaling rule (review #8) - §5 Spacing: explicit "single source of truth" note on slim banner bottom padding; new "Stats row" subsection capturing the ultraThinMaterial-on-small-cards-OK rule (review gap #10, #4) - §6 HomeView gradient: documented palette[0] defensive fallback when currentAccount is nil (review #13) - §7 Patterns: new "State variable naming" subsection with the showCapAlert / showAccountCapAlert / showConnectionCapAlert convention (review #7 doc side) - §11 Anti-patterns: audit-point note that ConnectSheet was the last surface to acquire .presentationBackground (review #9 doc side) Build green on iOS Simulator 26.4. pbxproj still 41 — assumes user hasn't yet archived 41; bump to 42 if needed before re-archive. Co-Authored-By: Claude Opus 4.7 (1M context) <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.
Summary
Bundles two independent fixes for combined build-20 device testing:
switch_relaysreturnsnull—LightSigner.processRequestnow returns("null", nil)instead of("[\"\(SharedConstants.relayURL)\"]", nil). Matches Amber's responder-only pattern and NDK default. Unblocks Coracle pairing (welshman stalls on non-null responses). Also includes a refactor (processRequestinternal) + new unit tests locking the wire format.80c4d4e+ecd9f89). Drops.localOnly: truefrom bunker URI copy; keeps 120s iPhone-side expiration. Nsec export stays locked. Enables iPhone → Mac paste for cross-device pairing.Pbxproj bumped to build 20.
Why they ship together
Testing the switch_relays change required verifying bunker flow on a Mac browser client (Coracle/noStrudel). The existing
.localOnly: trueblocked that cross-device paste, preventing the regression test. Cherry-picking the UC fix onto this branch let us verify both on one archive.Tradeoff noted: fevela signing regression (temporary)
switch_relays → nullremoves the accidental pool migration that nostr-tools-family clients (fevela, Primal, Snort) relied on to routesign_eventthroughrelay.powr.build. With null, those clients keep publishing sign_event to their own URI relays, which the proxy doesn't subscribe to. fevela nostrconnect signing will fail on this build.Resolution path: do not promote this to external TestFlight until proxy-per-client-relay V2 ships. V2 subscribes the proxy to each paired client's URI relays, catches sign_event regardless of which relay it lands on, and restores fevela signing while keeping Coracle pairing working.
Build 18 stays on external until V2 is ready. Build 20 is internal-only verification.
Test plan (build 20)
LightSignerProcessRequestTests(2 tests) pass; full suite 30/30 greenExpiration caveat (UC)
The 120s
.expirationDateonly applies to iPhone pasteboard. Apple's Universal Clipboard has no cross-device expiration mechanism. Mac-side clipboard stays populated until next copy. Mitigated bySharedStorage.rotateBunkerSecret()on successful pair. Documented in audit note A10.2.Merge note
Two merge paths:
🤖 Generated with Claude Code