Skip to content

fix: keep foreground inbox checks responsive - #769

Merged
jvsena42 merged 4 commits into
masterfrom
codex/paykit-ten-second-inbox
Sep 23, 2026
Merged

jvsena42 merged 4 commits into
masterfrom
codex/paykit-ten-second-inbox

Conversation

@ben-kaufman

@ben-kaufman ben-kaufman commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

This PR keeps foreground Paykit inbox checks on a ten-second interval while online so an idle inbox or failed check does not slow down subsequent requests.

Android counterpart: synonymdev/bitkit-android#1321

Description

  • Uses a fixed ten-second delay between periodic inbox rounds, including after failures, so one contact's error does not back off polling for everyone.
  • Skips periodic inbox and maintenance work while offline without accumulating a longer delay that survives reconnect.
  • Keeps contact discovery, endpoint maintenance and proof reconciliation on their slower schedule, and preserves the initial handshake burst and foreground lifecycle.
  • Reuses existing refresh handling and removes the success/failure result plumbing that was only needed for adaptive polling.

Out of Scope

  • Paykit SDK, private-payment resolution and UI: no changes. SDK peer sequencing and network timeouts remain unchanged, so a slow network call can still extend a round.
  • Background delivery: no new background polling or push mechanism.
  • Resource profiling: no real-device battery or network measurements. Steady-state idle polling increases from roughly two to six rounds per minute, while heavier maintenance remains unchanged.

Design

N/A — no UI changes.

Preview

N/A — no UI changes.

QA Notes

Manual Tests

  • 1. Two linked Paykit wallets → keep the receiving wallet open and idle beyond the initial burst → send a payment request: the existing Payment Request sheet opens after the next inbox check.
  • 2. Receiver → background the app → return to Wallet: foreground request discovery resumes without a new background polling loop.
  • 3. Receiver → disconnect the network after initial sync → reconnect → send a request: previously received requests remain available and polling does not retain a long offline backoff.

Automated Checks

  • PaykitPaymentRequestPollingScheduleTests.swift: the inbox delay remains ten seconds while maintenance follows thirty, sixty and 120-second intervals. The obsolete adaptive-backoff test was removed because inbox delay no longer depends on refresh results.
  • PaykitPaymentRequestServiceTests.swift: failed refreshes retain requests, recovery updates them, and SDK-reported peer errors do not discard received requests.
  • Simulator build and CI-style unit selection passed: 1,302 tests, zero failures, with the repository's six live-integration exclusions. Used a dedicated iPhone 17 Pro / iOS 26.1 simulator and separate build/dependency caches.
  • Scoped SwiftFormat and whitespace checks passed. Existing demo apps and the manual flows above were not changed or exercised.

@greptile-apps

greptile-apps Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

The PR should not merge until reconnecting resets or wakes the backed-off polling schedule, otherwise foreground request discovery can remain delayed for nearly two minutes after recovery.

Findings

  1. P1 Reconnect Leaves Polling Backed Off ▶

Summary

This PR changes foreground Paykit polling to keep successful inbox checks at ten-second intervals, introduces progressive failure backoff, and propagates per-peer intake failures without discarding successfully synchronized requests.

  • Separates inbox success/failure cadence from the slower maintenance cadence.
  • Preserves existing requests when synchronization fails and resets backoff after successful periodic refreshes.
  • Adds focused tests for polling intervals, failure recovery, and partial peer-intake errors.
  • The reconnect path does not currently communicate its successful refresh to the backed-off periodic schedule.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Foreground polling loop] --> B[Sleep for current delay]
  B --> C{Network connected?}
  C -- No --> D[Record failure and back off<br/>10 → 30 → 60 → 120 seconds]
  D --> B
  C -- Yes --> E[Refresh inbox]
  E --> F{Refresh succeeded?}
  F -- Yes --> G[Reset periodic delay to 10 seconds]
  F -- No --> D
  H[Network reconnect event] --> I[Immediate separate inbox refresh]
  I -. does not reset or wake schedule .-> B
Loading

Reviews (1) · Last reviewed commit: "fix: keep foreground inbox checks respon..."

Comment thread Bitkit/AppScene.swift Outdated

@ovi-reviewer ovi-reviewer Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Advice: ✅ Approve


Review: diff 4 files.
Counterpart synonymdev/bitkit-android#1321: equivalent.

Findings:
1 inline (non-blocking)

Audit:
Skipped - nothing a reviewer would report across 4 files (threshold 0.4; strongest Bitkit/AppScene.swift at 0.23).

Coverage:
QA: journeys and manual tests await all reviewers to approve, author can run it now via comment: @ovi-reviewer test


Reviewed by claude-opus-5-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner)

Comment thread BitkitTests/PaykitPaymentRequestPollingScheduleTests.swift

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No findings at bb4722aed. The net source change is AppScene.swift only; PaykitPaymentRequestService.swift returns to master. The new testPeerIntakeFailureDoesNotDropReceivedRequests pins behaviour that already existed.

Checked and clean:

  • No auto-pay. The poll path ends at showSheet(.send). contactPaymentRoute maps quickpay and amount to confirm whenever contactPaymentContext?.incomingPaymentRequest is set, so an incoming request always needs the user's confirm tap.
  • Overlapping polls. refresh() coalesces onto one refreshTask. Presentation is guarded by isPresentingRequests, the single-owner claimContactPaymentContext, and the presentationGeneration checks. Every trigger goes through these guards: the poll loop, the burst, scene-active, network-restored, proof changes and sheet dismiss.
  • Resurfacing. Dismissed and declined requests move to history. Subscription dismissals persist, and oneTimePending excludes approved and in-flight ids, so the next 10 s tick does not re-present a request the user just approved.
  • Cancellation from the new connectivity task id. refresh() runs unstructured. The presentation closure handles CancellationError and resets send state. This is the same surface that .task(id: scenePhase) already had.
  • Runaway. The loop sleeps a fixed 10 s. The [Bool] task id changes only on a real transition, and identity republish is throttled.
  • Parity with synonymdev/bitkit-android#1321. Both use the same cadence and reconnect behaviour. iOS cancels the loop while offline, and Android keeps ticking and skips each round with continue; the outcome is the same.

@ovi-reviewer ovi-reviewer Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Advice: ✅ Approve


Reaudit: diff 2 files.
No new findings; the rest is in the review.
Retest suggested: Tests 1-3 (the delta changes the polling round decision every item exercises).
Counterpart synonymdev/bitkit-android#1321: equivalent.

Coverage:
Unit tests: 85% - The new testOfflineRoundSkipsWorkWithoutAdvancingMaintenance covers the offline skip; only the SwiftUI task rekeying in Bitkit/AppScene.swift stays device-only.


Reviewed by claude-opus-5-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner)

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utAck

@jvsena42
jvsena42 enabled auto-merge September 22, 2026 11:47
ovi-reviewer[bot]
ovi-reviewer Bot previously requested changes Sep 22, 2026

@ovi-reviewer ovi-reviewer Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: ⛔️ Request Changes


Tests for the review: 1 of 3 manual tests passed.

QA:
Tested on two iOS 26.5 simulators (iPhone 17 Pro)

Test 1 ⛔️ failed: after the receiver idled beyond the initial burst, the sender showed the fresh 1,000-sat request as sent, but the receiver stayed on Wallet and never opened the Payment Request sheet.

evidence
1.mp4

Test 2 ✅ passed

evidence
2.mp4

Test 3 ⛔️ failed: two pending requests remained available offline and after reconnect, but the linked sender did not recover the Request Payment action in time, so a fresh post-reconnect request could not be submitted.

evidence
3.mp4

Tip

Worth a journey

Test 2
  • Leave pending payment requests undiscovered on the receiving wallet
  • Send the receiving wallet to the background
  • Return the receiving wallet to Wallet
  • Verify pending payment requests appear after foreground discovery resumes

Reviewed by gpt-5.6-sol-high via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner)

@ben-kaufman

Copy link
Copy Markdown
Contributor Author

These two failures do not show a polling regression yet. Test 1 used an unfunded payer: the Receiver769 evidence shows 0 sats, and the existing flow intentionally keeps unaffordable requests from opening the payment sheet.

For Test 3, Receiver769 successfully sent the request shown in the video, while the missing Request Payment action is on Sender769. A remote peer coming back does not restart eligibility maintenance on Sender769, which can take up to the existing 120-second interval and was not changed here.

Could we rerun Test 1 with the payer funded, then rerun Test 3 from the wallet that actually disconnects and reconnects? If the other wallet also needs to regain Request Payment within 60 seconds, that is a separate endpoint eligibility change.

@ovi-reviewer

ovi-reviewer Bot commented Sep 22, 2026

Copy link
Copy Markdown

Could we rerun Test 1 with the payer funded, then rerun Test 3 from the wallet that actually disconnects and reconnects? (comment)

Comment @ovi-reviewer retest 1 3 to start the rerun. It follows your comment: Test 1 runs with the payer funded, and Test 3 disconnects and reconnects the sender.

@ovitrif
ovitrif dismissed ovi-reviewer[bot]’s stale review September 22, 2026 22:37

test issue apparently

@ovitrif

This comment was marked as outdated.

1 similar comment
@ben-kaufman

Copy link
Copy Markdown
Contributor Author

@ovi-reviewer retest 1 3

@ovi-reviewer ovi-reviewer Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: ✅ Approve


Retest for the review: Tests 1 and 3 pass now.
Set up as described in comment.

QA:
Tested on two iOS 26.5 simulators (iPhone 17 Pro), regtest
Test 2 already done in review.

Test 1 ✅ passed

evidence
1.mp4

Test 3 ✅ passed

evidence
3.mp4

Tip

Worth a journey

Test 1
  • Fund the payer wallet through Blocktank Regtest
  • Link the two Paykit profiles as contacts
  • Keep the payer on Wallet beyond the initial burst
  • Send a payment request from the other wallet
  • Verify the Payment Request sheet opens after the next inbox check
Test 3
  • Keep two requests pending on the payer wallet
  • Disconnect the wallet that sends payment requests after initial sync
  • Reconnect the request-sending wallet
  • Send another payment request from the reconnected wallet
  • Verify prior requests remain and the new request opens after the next check

Reviewed by gpt-5.6-sol-high via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner)

@ovitrif ovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utAck

@jvsena42
jvsena42 merged commit 0a3a6f0 into master Sep 23, 2026
13 checks passed
@jvsena42
jvsena42 deleted the codex/paykit-ten-second-inbox branch September 23, 2026 00:07
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.

3 participants