CP-14651: subscribe a new recurring swap for push notifications without needing a screen visit - #4015
CP-14651: subscribe a new recurring swap for push notifications without needing a screen visit#4015B0Y3R-AVA wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes recurring swap push-notification subscription timing by ensuring the staggered post-broadcast React Query invalidations actually refetch the cached RECURRING_SCHEDULES query even when no observers are mounted (e.g., after the swap modal dismisses), so ensureOrderSubscriptions can see the newly indexed order and subscribe the device.
Changes:
- Update
scheduleStaggeredInvalidateto invalidate withrefetchType: 'all'so inactive-but-cached queries refetch during the catch-up window. - Add a regression unit test that drives a real
QueryClientto cover the “observer unmounts before indexer catches up” scenario. - Refresh an explanatory comment in
_makeOrderActionHook.tsto reflect the new refetch behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| packages/core-mobile/app/new/features/recurringSwap/utils/staggeredInvalidate.ts | Forces staggered invalidations to refetch inactive cached queries (refetchType: 'all') to catch indexer lag after modal dismissal. |
| packages/core-mobile/app/new/features/recurringSwap/store/subscribeAfterCreate.test.ts | Adds regression tests verifying subscription occurs even when the schedules observer is torn down before the order appears. |
| packages/core-mobile/app/new/features/recurringSwap/hooks/_makeOrderActionHook.ts | Updates commentary around staggered invalidation semantics and account-scoped query keys. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // marking it stale — which is the intent here: the catch-up must settle | ||
| // the status of the order the user acted on, whichever account is active | ||
| // by the time it fires. The builder lowercases the owner so this |
There was a problem hiding this comment.
Fair catch, the sentence was ambiguous. "whichever account is active by the time it fires" was meant as a concessive (i.e. regardless of what is active), but it reads just as easily as describing the refetch target, which is the opposite of what happens.
Reworded in 97aae14 to state the behaviour positively instead: the call always targets the acting account (the owner of the order that was cancelled/paused/resumed) and never the account that happens to be active when the timer fires. Since the whole point of this comment is to stop a future reader from misreading the cross-account behaviour, leaving it ambiguous defeated the purpose.
Coverage report ✅2/2 packages passed thresholds 🟢 @avalabs/core-mobile
🟢 @avalabs/k2-alpine
Artifacts and threshold sources
Source run: Mobile PR |
… new recurring order subscribes for push without needing a screen visit
…ting account, never the active one
97aae14 to
6ab8398
Compare
Description
Ticket: CP-14651
Reported as "recurring swap notifications don't come through on Android". It is not an Android bug. The deciding factor is whether a
RECURRING_SCHEDULESReact Query observer is mounted when alistOrdersrefetch could land.The client has no orderId at broadcast time (
executeFirstFillreturns only{ txHash }), so the only route to a push subscription isensureOrderSubscriptionsseeing the order in a landedlistOrderssnapshot.submitRecurringSwapinvalidates at broadcast, but Markr's indexer lags by a few seconds, so that snapshot comes back empty.scheduleStaggeredInvalidatequeues catch-up invalidates at t=5/15/30s to cover the lag — and thenSwapScreencallsdismissAll()the momentsubmitRecurringSwapresolves.invalidateQueriesdefaults torefetchType: 'active'. With the modal gone there is no observer, so all three catch-up timers marked the query stale and refetched nothing. The order never landed in the cache, the schedules cache subscriber never fired, and the device was never subscribed. Because the notification-history row is written per subscribed device (webhook/recurring-swaps→createNotificationHistoryAfterSend(subscription.clientId, …)), every leg executing before the user's next visit to Activity / Swap / the schedules screen was silently dropped: no push and no notification-center row.Fix is one line — the catch-up batch now uses
refetchType: 'all'so it refetches the cached-but-inactive query. Also updates a now-stale comment in_makeOrderActionHook.tsthat described the scoped invalidate as "a harmless stale-mark", which is no longer accurate.Measured on a Pixel 8 Pro (mainnet C-Chain, user stayed on Portfolio throughout):
No dependencies introduced. Not breaking.
Screenshots/Videos
Device logs are the meaningful artifact here rather than UI. Before the fix, order
0x579c0ad8…:After the fix, order
0x7a7045de…:Notification center before the fix showed no row for the leg that executed at creation, and rows for every leg after the subscribe timestamp (Swap 2 at 2:50, Swap 3 at 2:56, Swap 4 at 3:02, Swap 5 at 3:08 Completed).
Testing
Dev Testing
iOS: 9421
Android: 9422
Happy path (this is the reported repro):
/v1/push/recurring-swaps/subscribeand get a 200.Before this change, step 2 never happened and step 3 produced nothing until you visited a schedules-observing screen.
Edge cases covered by unit tests in
subscribeAfterCreate.test.ts, which drives a realQueryClient:Also exercised: cancel / pause / resume still settle correctly via
_makeOrderActionHook(scheduleStaggeredInvalidateis shared).yarn testforrecurringSwap,notifications,services/notifications: 275 passing / 26 suites. tsc and eslint clean for the changed files.QA Testing
Acceptance criteria: after creating a recurring swap, you receive notifications for subsequent legs without opening the recurring schedules screen or the Activity tab.
Note for QA: the very first swap executes at order-creation time, before any subscription can exist, so its notification-center row is still expected to be missing. That is a known backend follow-up, not a regression in this PR.
Checklist
Please check all that apply (if applicable)
Follow-up (needs backend)
Leg 1 executes inside
executeFirstFillat order creation, before the subscription exists, sogetActiveSubscriptionsByOrderIdreturns empty and no history row is written. Confirmed on device. A client cannot subscribe to an order before that order exists, so this race is structural to per-(orderId, deviceArn)subscriptions. Two options for the notification-sender service:Option 2 preferred.