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

@DocNR DocNR commented Apr 28, 2026

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 main Apr 28, 2026
@DocNR DocNR deleted the fix/register-retry-and-aggressive-sweep branch April 28, 2026 16:20
Sign up for free to 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