Skip to content

fix: register-retry on cellular failure + aggressive blank-NC sweep - #16

Merged
DocNR merged 1 commit into
mainfrom
fix/register-retry-and-aggressive-sweep
Apr 28, 2026
Merged

fix: register-retry on cellular failure + aggressive blank-NC sweep#16
DocNR merged 1 commit into
mainfrom
fix/register-retry-and-aggressive-sweep

Conversation

@DocNR

Copy link
Copy Markdown
Owner

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`:

    • `registerWithProxy()` records `lastRegisterSucceededAtKey` / `lastRegisterFailedAtKey` timestamps in app-group defaults.
    • New `ensureRegisteredFresh()` gates re-registers: skips if last success < 30 min ago, applies 60s cooldown after failures so a dead proxy doesn't get hammered on every foreground.
  • `Shared/SharedConstants.swift`: adds the two new defaults keys.

Test plan

  • `xcodebuild build` → BUILD SUCCEEDED
  • `xcodebuild test` → TEST SUCCEEDED
  • Device test (build 27):
    • On bad cellular: launch Clave, observe registration fails silently. Move to wifi, foreground Clave. Verify `ensureRegisteredFresh()` retries — check proxy log for `[HTTP] Registered` line.
    • Open NC after ~10 min of Clave backgrounded with active signing happening. Should be empty (sweep ran on .inactive transitions).
    • If blanks DO show up briefly, opening Clave once should clear them (sweep on .active).
    • Hot-loop guard: with proxy unreachable, background/foreground 5x in 60 sec. Verify only 1 register attempt fires (60s failure cooldown).

Closes

BACKLOG: "registerWithProxy() retry on transient network failure" (opened earlier today, fixed same day).

🤖 Generated with Claude Code

…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>
@DocNR
DocNR merged commit 2786f9f into mainApr 28, 2026
@DocNR
DocNR deleted the fix/register-retry-and-aggressive-sweep branch April 28, 2026 16:20
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@DocNR