Skip to content

fix(native): refetch stale queries on app resume#2492

Merged
kushagrasarathe merged 2 commits into
mainfrom
fix/native-activity-refresh
Jul 24, 2026
Merged

fix(native): refetch stale queries on app resume#2492
kushagrasarathe merged 2 commits into
mainfrom
fix/native-activity-refresh

Conversation

@innolope-dev

@innolope-dev innolope-dev commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Problem

The Android app's home-screen Activity widget can show weeks-old transactions while web shows the fresh list for the same account.

TanStack Query's refetchOnWindowFocus relies on visibilitychange, which Android WebViews don't reliably fire when the app resumes from background. An app process kept alive/frozen by the OS resumes with its pre-background in-memory query data and never refetches — there is currently no native lifecycle → query-refetch wiring anywhere in the app.

Fix

  • useNativePlugins: listen to Capacitor appStateChange and drive TanStack's focusManager, so stale queries refetch on every app resume (the canonical TanStack pattern for non-browser hosts). Registered first among the App listeners so a deep-link init failure can't skip it.
  • useTransactionHistory: send the history request with cache: 'no-store' — defense in depth alongside the API-side Cache-Control: no-store (companion peanut-api-ts PR).
  • Drop stale "SW disk cache (1 week)" comments: the service worker no longer intercepts API responses (verified against the built public/sw.js).

Verification

  • tsc --noEmit clean; prettier run on touched files.
  • On-device check after release: background the app, make a transaction from web, resume the app → Activity should refresh within the 30s staleTime window.

Notes

Ships in the next binary/OTA release; no API contract changes.

Summary by CodeRabbit

  • Bug Fixes
    • Improved data freshness for transaction history by preventing stale HTTP responses from being reused.
    • Improved query updates when the app moves between active and inactive states.
    • Preserved offline access to previously loaded data through in-memory caching.

TanStack Query's refetchOnWindowFocus keys off visibilitychange, which
Android WebViews don't reliably fire on resume — an app restored from
background kept rendering its pre-background query data, so the home
Activity widget showed weeks-old transactions. Drive the focusManager
from Capacitor's appStateChange so stale queries refetch on resume.

Also send the history fetch with cache: 'no-store' (server now sets
Cache-Control: no-store too) and drop stale service-worker-cache
comments — the SW no longer intercepts API responses.
@innolope-dev innolope-dev self-assigned this Jul 23, 2026
@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Jul 23, 2026 1:43pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 11 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2380a545-46d1-4c06-a44e-7e45afd874f3

📥 Commits

Reviewing files that changed from the base of the PR and between 50e5080 and 9701b08.

📒 Files selected for processing (1)
  • src/hooks/useNativePlugins.ts
📝 Walkthrough

Walkthrough

Changes

Query freshness

Layer / File(s) Summary
Native lifecycle focus synchronization
src/hooks/useNativePlugins.ts
Capacitor app state events now update React Query focus state, and teardown removes the listener and resets focus.
Transaction history cache policy
src/hooks/useTransactionHistory.ts, src/config/wagmi.config.tsx
History requests explicitly use cache: 'no-store', while comments describe in-memory query caching and existing offline behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CapacitorApp
  participant useNativePlugins
  participant focusManager
  CapacitorApp->>useNativePlugins: appStateChange(isActive)
  useNativePlugins->>focusManager: setFocused(isActive)
  useNativePlugins->>CapacitorApp: remove listener during teardown
  useNativePlugins->>focusManager: setFocused(undefined) during teardown
Loading

Suggested reviewers: kushagrasarathe

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: native app resume now triggers refetches for stale queries.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/native-activity-refresh

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6242.76 → 6243.1 (+0.34)
Findings: 0 net (+11 new, -11 resolved)

🆕 New findings (11)

  • high complexity — src/hooks/useNativePlugins.ts — CC 30, MI 62.73, SLOC 149
  • medium high-mdd — src/hooks/useNativePlugins.ts:20 — useNativePlugins: MDD 34.1 (uses across many lines from declarations)
  • medium high-dlt — src/hooks/useNativePlugins.ts:20 — useNativePlugins: DLT 33 (calls 33 distinct functions — high context load)
  • medium high-mdd — src/hooks/useNativePlugins.ts:23 — : MDD 32.0 (uses across many lines from declarations)
  • medium high-dlt — src/hooks/useNativePlugins.ts:23 — : DLT 31 (calls 31 distinct functions — high context load)
  • medium high-mdd — src/hooks/useNativePlugins.ts:44 — init: MDD 20.3 (uses across many lines from declarations)
  • medium complexity — src/hooks/useTransactionHistory.ts — CC 17, MI 58.13, SLOC 68
  • low high-dlt — src/hooks/useNativePlugins.ts:44 — init: DLT 24 (calls 24 distinct functions — high context load)
  • low high-mdd — src/hooks/useTransactionHistory.ts:52 — useTransactionHistory: MDD 13.2 (uses across many lines from declarations)
  • low missing-return-type — src/config/wagmi.config.tsx:50 — ContextProvider: exported fn missing return type annotation
  • low missing-return-type — src/hooks/useNativePlugins.ts:20 — useNativePlugins: exported fn missing return type annotation

✅ Resolved (11)

  • src/hooks/useNativePlugins.ts:19 — useNativePlugins: DLT 30 (calls 30 distinct functions — high context load)
  • src/hooks/useNativePlugins.ts:19 — useNativePlugins: MDD 29.3 (uses across many lines from declarations)
  • src/hooks/useNativePlugins.ts:22 — : MDD 27.8 (uses across many lines from declarations)
  • src/hooks/useNativePlugins.ts — CC 26, MI 61.91, SLOC 132
  • src/hooks/useNativePlugins.ts:36 — init: MDD 21.0 (uses across many lines from declarations)
  • src/hooks/useTransactionHistory.ts — CC 17, MI 58.27, SLOC 67
  • src/hooks/useNativePlugins.ts:22 — : DLT 28 (calls 28 distinct functions — high context load)
  • src/hooks/useNativePlugins.ts:36 — init: DLT 23 (calls 23 distinct functions — high context load)
  • src/hooks/useTransactionHistory.ts:52 — useTransactionHistory: MDD 12.2 (uses across many lines from declarations)
  • src/config/wagmi.config.tsx:51 — ContextProvider: exported fn missing return type annotation
  • src/hooks/useNativePlugins.ts:19 — useNativePlugins: exported fn missing return type annotation

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2062 ran, 0 failed, 0 skipped, 34.8s

📊 Coverage (unit)

metric %
statements 60.1%
branches 43.9%
functions 49.0%
lines 60.5%
⏱ 10 slowest test cases
time test
3.7s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.2s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.5s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.3s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › keeps stickers off the username pill (final pass respects the keep-out)
0.3s src/app/(mobile-ui)/withdraw/__tests__/withdraw-states.test.tsx › Bank withdrawal keeps the $1 minimum for sub-$1 amounts
0.3s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.2s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in validateInviteCode body
0.2s src/utils/__tests__/demo-balance.test.ts › auto-refills a wallet older than the TTL on cold start
0.2s src/utils/__tests__/demo-balance.test.ts › resetDemoBalance refills and restarts the TTL window
0.2s src/utils/__tests__/demo-balance.test.ts › starts at the full balance on a fresh install and stamps a timestamp
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/useNativePlugins.ts`:
- Around line 45-51: Update the effect’s awaited native listener registrations
in useNativePlugins to track whether cleanup has already run; when
App.addListener or any other awaited registration resolves after disposal,
immediately remove the late handle instead of storing it, and ensure cleanup
purges all registered handles while preserving the existing focus, back, and
link handler behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 09093127-8f5e-4b17-9b3b-8f2c099f9411

📥 Commits

Reviewing files that changed from the base of the PR and between 5fd5969 and 50e5080.

📒 Files selected for processing (3)
  • src/config/wagmi.config.tsx
  • src/hooks/useNativePlugins.ts
  • src/hooks/useTransactionHistory.ts
💤 Files with no reviewable changes (1)
  • src/config/wagmi.config.tsx

Comment thread src/hooks/useNativePlugins.ts
If the effect cleans up while a listener registration is still in
flight, the handle resolved after teardown and was never removed.
Route every registration through a disposed-aware track() helper.

@kushagrasarathe kushagrasarathe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed adversarially (external contributor, native lifecycle) — SAFE, approving with conditions.

Uses the canonical TanStack focusManager.setFocused() API to re-run stale mounted queries on app resume — correct choice over hand-rolled invalidation. Verified:

  • Not a refetch storm — only mounted + stale queries refire, staleTime is 30s everywhere, so a resume within 30s is a no-op; the mounted set on a route is bounded. No money mutation is focus-driven.
  • Listener leak fixed — the disposed/track() guard correctly handles the async App.addListener teardown race.
  • Web-safe — whole effect is gated behind if (!isCapacitor()) return; the browser visibilitychange path is untouched.

Conditions (non-blocking, please confirm):

  1. setFocused(false) on background pins isFocused() false app-wide, which pauses any refetchInterval polling (balance, deposit-polling) while backgrounded. For a frozen WebView that's arguably desired — but it's an undocumented side effect; add a one-line comment or confirm intent.
  2. Re-run @coderabbitai review — the automated review was rate-limited to zero line comments here, and this is external code to a native prod path.
  3. Verify sequencing once #2489 (app-lock) merges — both fire on appStateChange; confirm no double-trigger / stale-balance flash.

Nit: the teardown setFocused(undefined) can fire a spurious app-wide refetch if the last value was true — harmless (layout rarely unmounts) but pointless.

@kushagrasarathe
kushagrasarathe merged commit 78979d5 into main Jul 24, 2026
23 of 25 checks passed
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.

2 participants