fix: register-retry on cellular failure + aggressive blank-NC sweep#16
Merged
Conversation
…C sweep Two real bugs from build 26 testing today: 1. **registerWithProxy silently drops failures on bad cellular.** User launched Clave on weak cellular signal; the auto-register HTTP POST (10s timeout) failed, the failure was dropped because the auto- register call sites in `AppState.init` (`.apnsDeviceTokenAvailable` observer) and `loadState()` pass `completion: nil`. Token never reached proxy. Symptom: signing silently failed until user moved to wifi and tapped Settings → Register manually (the manual path surfaces the failure to the UI). PR #15 was correct on the happy path but didn't self-heal when the initial POST failed. 2. **Blank Notification Center entries weren't being swept.** Build 26 shipped `MainTabView.sweepBlankNotifications()` filtering on `title.isEmpty`. But the proxy's APNs payload sets `alert: { title: " ", body: " " }` (single SPACE characters, so NSE has something to override). When NSE doesn't run (cold-launch race, timeout, force-quit recovery), iOS keeps the proxy's original payload — title is " " (single space), NOT empty. Sweep never matched these. User reported 8 blank entries accumulating in NC while Clave was backgrounded. Changes: * New `Clave/Views/Components/NotificationCenterSweep.swift` extracts `sweepBlankNotifications()` to a top-level free function so both `MainTabView` (scenePhase observer) and `ForegroundRelaySubscription` (L1 event dispatch) can call it. Filter now trims whitespace and checks BOTH title AND body, catching the proxy single-space fallback. * `MainTabView.handleScenePhase` now sweeps on `.inactive` too (catches the case where a user opens NC via swipe-down while Clave is the most-recent foreground app but isn't currently `.active`). Also calls `appState.ensureRegisteredFresh()` on `.active`. * `Shared/ForegroundRelaySubscription.swift` calls `sweepBlankNotifications()` after each in-process event. Catches the case where Clave is foregrounded and a parallel APNs push leaves a blank NC entry from NSE's `.noEvents` return path. * `Clave/AppState.swift` `registerWithProxy()` now records `lastRegisterSucceededAtKey` / `lastRegisterFailedAtKey` timestamps in `SharedConstants.sharedDefaults`. New `ensureRegisteredFresh()` method gates re-registers: skips if last success < 30 min ago, applies a 60s cooldown after failures so a dead proxy doesn't get hammered on every foreground. * `Shared/SharedConstants.swift` adds the two new defaults keys. Verification: - xcodebuild -scheme Clave -destination 'generic/platform=iOS' build → BUILD SUCCEEDED - xcodebuild test on iPhone 17 Pro Max sim (iOS 26.4) → TEST SUCCEEDED - Device test (build 27): on bad cellular, launch Clave, verify ensureRegisteredFresh() retries on next foreground after network recovery. Verify NC stays clean of blank entries even with Clave backgrounded for ~30 min during a session. Closes BACKLOG: "registerWithProxy() retry on transient network failure" (opened today, fixed same day). 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
Two real bugs surfaced during build 26 testing today:
1. `registerWithProxy` silently drops failures on bad cellular
User launched Clave on weak cellular signal; the auto-register HTTP POST (10s timeout) failed, the failure was silently dropped because the auto-register call sites in `AppState.init` (`.apnsDeviceTokenAvailable` observer) and `loadState()` pass `completion: nil`. Token never reached proxy. Symptom: signing silently failed until user moved to wifi and tapped Settings → Register manually (the manual path surfaces the failure to the UI).
PR #15 was correct on the happy path but didn't self-heal when the initial POST failed.
2. Blank NC entries weren't being swept
PR #13 shipped `MainTabView.sweepBlankNotifications()` filtering on `title.isEmpty`. But the proxy's APNs payload sets `alert: { title: " ", body: " " }` (single SPACE characters, so NSE has something to override). When NSE doesn't run (cold-launch race, timeout, force-quit recovery), iOS keeps the proxy's original payload — title is " " (single space), NOT empty. Sweep never matched these. User reported 8 blank entries accumulating in NC while Clave was backgrounded for ~30 min during a session.
Changes
New `Clave/Views/Components/NotificationCenterSweep.swift`: extracts `sweepBlankNotifications()` to a top-level free function so both `MainTabView` and `ForegroundRelaySubscription` can call it. Filter now trims whitespace and checks BOTH title AND body, catching the proxy single-space fallback.
`MainTabView.handleScenePhase`: sweeps on `.inactive` too (catches user opening NC via swipe-down while Clave is most-recent foreground app but not `.active`). Also calls `appState.ensureRegisteredFresh()` on `.active`.
`Shared/ForegroundRelaySubscription.swift`: calls `sweepBlankNotifications()` after each in-process event. Catches the case where Clave is foregrounded and a parallel APNs push leaves a blank NC entry from NSE's `.noEvents` return.
`Clave/AppState.swift`:
`Shared/SharedConstants.swift`: adds the two new defaults keys.
Test plan
Closes
BACKLOG: "registerWithProxy() retry on transient network failure" (opened earlier today, fixed same day).
🤖 Generated with Claude Code