feat(mobile): port activity feed store and create ActivityFeed component - #528
Conversation
- Create frontend/mobile/lib/activityFeed.ts with hydrate/append lifecycle ported from frontend/wallet - Wire to Wraith GET /transfers/:address endpoint with 15s polling - Create ActivityFeed component with filter pills, skeleton loading, swap display - Update mobile dashboard to integrate the feed Closes Miracle656#461
|
@Belzabeem is attempting to deploy a commit to the miracle656's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@Belzabeem Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Miracle656
left a comment
There was a problem hiding this comment.
The store design is sound. startPolling guarding against a redundant restart and calling stopPolling() before rebinding is the part people usually get wrong — duplicate intervals stacking on every remount is the classic bug here, and you closed it:
if (_pollInterval && _currentAddress === address && _wraithUrl === wraithUrl) {
return; // already polling this address
}
stopPolling();The hydrate / append split and the subscribeActivityFeed listener pattern are the right shape too.
Three things to sort out.
1. This takes over app/index.tsx, which is the entry route
app/index.tsx (114+/14-) becomes the activity-feed home screen. But that file is the app's routing entry point, and it's the most contended file in the mobile queue — four other open PRs touch it:
- #507 makes it a redirect into the
(tabs)group - #516 deletes it in favour of
(tabs)/index.tsx - #520 makes it session-aware: wallet → dashboard, first run → welcome
- this PR makes it the activity feed screen
Only one can win, and it needs to be the routing one — a user opening the app has to land on onboarding or the dashboard depending on whether they have a wallet, which is #520's logic.
The activity feed is dashboard content, not the entry route. Once #507 lands, its home is app/(tabs)/dashboard.tsx. Please move <ActivityFeed /> and the useInitActivityFeed call there and leave app/index.tsx alone. Landing order is #507 → #520 → this.
Your components/ActivityFeed.tsx and lib/activityFeed.ts are unaffected — this is purely about where they get mounted.
2. It polls a demo address, not the user's wallet
const DEMO_ADDRESS = process.env.EXPO_PUBLIC_DEMO_ADDRESS ?? null;
const { loading } = useInitActivityFeed(DEMO_ADDRESS, WRAITH_URL);If EXPO_PUBLIC_DEMO_ADDRESS is set in a build, every user sees that account's transaction history presented as their activity feed. If it isn't set, the feed is permanently empty.
I've had to flag variations of this on #515 (fixture NFTs shown as holdings) and #521 (stubbed vault showing fake transaction hashes), so let me state the general rule: never render another account's financial data, or fabricated data, as the user's own.
The address should come from wallet state. #520 reads it as invisible_wallet_address from storage — that's the source to use. A demo address is fine as a dev-only override, but it must not be the primary path, and the feed should render its empty state when there's no wallet.
3. fetch has no timeout
const res = await fetch(url);On a 15-second interval with no abort, a stalled request on a flaky mobile connection leaves a pending fetch while the next tick fires — they accumulate. Other modules in this tree already use AbortSignal.timeout(...) (see lib/sep24.ts in #522), so:
const res = await fetch(url, { signal: AbortSignal.timeout(10_000) });Two notes, not blocking
No AppState handling. The interval runs regardless of whether the app is foregrounded. iOS suspends JS timers when backgrounded so the practical impact is limited, but the flip side matters more: on resume there's no immediate refetch, so the user sees stale data for up to 15 seconds. Subscribing to AppState to pause on background and refetch on foreground would fix both.
Network switching. #526 adds runtime testnet/mainnet switching, and its own docs note that a switch invalidates everything fetched from the previous chain. This feed caches records in module state with no network key, so after a switch it would show the old chain's transactions. Worth keying the cache on network once #526 lands.
No tests. A jest.config.js arrives with #524/#525. hydrate / append dedup and the startPolling guard are all pure logic and easy to cover.
…se wallet address from store, add fetch timeout
- Restore QuickActions on the dashboard. This branch predates Miracle656#517 and its dashboard rewrite dropped the component and its import, which would have removed the quick-actions row. - fetchTransfers swallowed every failure and returned [], so an unreachable indexer, an HTTP error and a genuinely empty wallet all rendered as "No transactions yet." It now throws; useInitActivityFeed already catches into its error state, which was being returned but never used. - ActivityFeed takes an error prop and renders a distinct message, and the dashboard passes it through. tsc clean; jest 11 suites / 198 tests; expo lint clean.
The lockfile auto-merged into a state npm 10 rejected: npm error `npm ci` can only install packages when your package.json and package-lock.json ... are in sync npm error Missing: @jest/transform@29.7.0 from lock file Regenerated with npm 10 so it matches what CI installs.
|
Merging. The store is well built — an external-store subscription with Three changes before merge: Restored Failures were being reported as an empty wallet. if (!res.ok) {
console.warn(`[activityFeed] Wraith returned ${res.status}`);
return [];
}
...
} catch (err) {
console.warn('[activityFeed] fetchTransfers failed:', err);
return [];
}So an unreachable indexer, a 500, a malformed response and a genuinely empty wallet all rendered identically as "No transactions yet." On a wallet screen that's a meaningful lie — a user checking whether a payment arrived would be told it hadn't. Interestingly Lockfile. Verified: Two follow-ups worth raising as issues rather than holding this up:
|
feat(mobile): port activity feed store and create ActivityFeed component
Closes #461