Skip to content

Visual and performance revamp: theming, store split, pipeline, pagination#606

Merged
jamespepper81 merged 22 commits into
mainfrom
dev
Jul 12, 2026
Merged

Visual and performance revamp: theming, store split, pipeline, pagination#606
jamespepper81 merged 22 commits into
mainfrom
dev

Conversation

@jamespepper81

Copy link
Copy Markdown
Contributor

No description provided.

claude and others added 22 commits July 2, 2026 19:56
…port

- New hooks/theme-store.tsx: light/dark/system modes, defaults to system,
  persists under the existing 'theme' key so saved choices carry over
- wallet-store re-exports theme/toggleTheme from the new context, so all
  existing useWallet() call sites keep working while theme-only consumers
  can subscribe without re-rendering on wallet data polls
- Tab bar no longer remounts on every theme object change (keys on isDark)
- Theme-only screens and hot components (Toast, SkeletonLoader) switched
  to useTheme(); Toast/SkeletonLoader hardcoded greys replaced with tokens
- Settings gains a 'Match System Appearance' toggle
- Remove dead theme helpers (triggerHapticFeedback, fun-style factories)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
- GradientBackground light mode now derives from theme tokens (was a
  hardcoded coral/peach family unrelated to the theme)
- ErrorBoundary fallback uses the current brand palette
- New shared ScreenLoading component (theme-aware spinner) replaces four
  copy-pasted off-theme 'Loading...' fallbacks in the tab screens
- Default wallet color is Bitcoin orange; new 'bitcoin' palette entry
- WalletCard fallback gradient and destructive menu colors use tokens
- fee-settings congestion colors, generate-xpub privacy warning, and
  +not-found screen switched from hardcoded hex to theme tokens

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
- getWalletData now dedupes in-flight calls per xpub and memoizes successful
  results for 25s, collapsing the 3 parallel pipeline runs per 30s poll into
  one; pull-to-refresh and wallet erase clear the memo explicitly
- Polling pauses while the app is backgrounded (refetchOnWindowFocus still
  catches up on foreground); address discovery poll relaxed to 60s
- BalanceChart history is computed in a single O(n) pass and memoized, so it
  no longer recomputes O(points x transactions) on every store update
- TransactionItem rows are memoized behind a thin store-subscribing wrapper
  and no longer re-render on every poll; entrance stagger capped at 8 rows
- Transaction history renders through a virtualized FlatList instead of
  ScrollView + map over every transaction
- Home wallet carousel gets getItemLayout and stable wallet-id keys
- Verbose UTXO-serializing logs in the send path gated behind __DEV__

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
- New components/AppButton.tsx: Pressable with spring press-scale, haptic
  feedback, themed primary/secondary/outline/destructive variants built on
  createButtonStyle, plus loading and disabled states and an icon slot
- Adopted on the home Send/Receive quick actions, receive screen's Setup
  Wallet / New Address / Copy / Share buttons, and the send screen's
  Review & Send button; per-screen duplicate button styles removed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
- New components/TransactionReviewSheet.tsx: formSheet modal with a
  proper review state (amount + fiat, full monospace recipient, network
  fee, fee rate + time estimate, RBF badge, total, irreversibility
  warning) and a success state (animated checkmark, copyable txid,
  View Transaction / Done actions)
- Send screen opens the sheet instead of the emoji Alert; the biometric
  gate and handleSendTransaction sequence are unchanged, and the old
  Alert's form-clear/navigation callbacks moved to the sheet actions
- Confirm is disabled while broadcasting, preventing double-submits
- Send form is now wrapped in KeyboardAvoidingView so the keyboard no
  longer covers the amount and custom-fee inputs
- SuccessAnimation (previously unused) is wired in with the theme's
  success color

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
- Settings currency / auto-lock / wallet pickers converted from
  transparent JS slide-up modals to presentationStyle formSheet with
  themed backgrounds, theme-token dividers, and selection haptics
- Home edit-wallet modal simplified to a single formSheet layout
  (removes the duplicated iOS-26/fallback branches) using AppButton
  for Cancel/Save
- manage-wallets edit modal converted the same way

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
Security:
- New services/secure-pin-service.ts stores the unlock PIN in the
  hardware-backed keystore instead of plaintext AsyncStorage, with a
  silent one-time migration of any existing PIN; all three PIN readers
  (auto-lock store, PIN verification screen, security test service) now
  go through it, and wallet erase deletes the secure copy
- wallet-setup no longer falls back to the publicly-known 'abandon...'
  test mnemonic when crypto fails to load - mnemonic generation and
  validation now fail closed and block wallet creation

Hygiene:
- Delete dev screen app/mfa-test.tsx from the route tree
- Delete 9 orphaned components that were never imported (BitSleuthButton,
  BitSleuthCard, PremiumButton, AnimatedNumber, AnimatedPressable,
  OptimizedTouchableOpacity, LoadingAnimation, ConfettiCelebration,
  EmojiReaction); SuccessAnimation was kept and is now in use
- Remove unused nativewind and zustand dependencies
  (react-native-confetti-cannon stays: wallet-setup uses it directly)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
- docs/REVAMP_NOTES.md: theme architecture, color rules, getWalletData
  memo contract, PIN migration behavior, and the deferred-work list
- Add feedback entries to frontend-design and react-native-skills
  SKILL_MEMORY files per the repo's skills feedback loop

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
- wallet-addresses and coin-control now render through FlatList (stable
  keys, initialNumToRender/windowSize/removeClippedSubviews) with their
  summary/quick-action blocks as list headers and the educational cards
  as footers, instead of mapping every row inside a ScrollView
- wallet-addresses copy/refresh feedback switched from Alert to the
  shared toast
- Receive screen informational messages (rate-limit wait, generation
  failure, unexpected error) now use the toast, and a success toast
  confirms new address generation; the gap-limit education alert stays
  as an alert since it needs the full explanation read

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
The store body (queries, mutations, state - unchanged) now runs inside a
hand-rolled WalletProvider that publishes each of its nine memoized slices
plus a small meta slice through its own context (hooks/wallet-contexts.tsx).
New narrow hooks (useWallets, useWalletBalance, useWalletTransactions,
useWalletUtxos, useWalletSettings, useWalletActions, useCoinControl, ...)
let components subscribe to only the data they render.

useWallet() remains as a compatibility hook with the exact legacy shape and
outside-provider behavior (returns undefined for the screens that gate on
it), so all existing call sites are untouched and re-render exactly as
before; converting consumers to the narrow hooks is the follow-up commit.

@nkzw/create-context-hook stays: auto-lock-store still uses it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
- WalletSelector, WalletCard, QRScanner, PIN screens, settings, coin-control,
  send, home, and TransactionItem's wrapper now subscribe via the narrow
  context hooks (and useTheme) instead of the all-slices useWallet(), so
  components that render no polled data stay idle across the 30s refreshes
  and the send form no longer re-renders mid-typing on unrelated churn
- Wrapper availability gates read only the low-churn WalletsContext
- balanceData no longer embeds the priceQuery object in its memo value/deps
  (it gave BalanceContext a new identity on every store render);
  address-details reads bitcoinPrice instead of priceQuery.data

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
- getWalletData no longer issues /address/{addr} stats per used address:
  the wallet balance is computed from UTXOs and address activity comes
  from the txs response (a cache hit from discovery), so the per-address
  wire cost drops from 2 requests to 1. The addresses screen keeps its
  chain_stats calls, which are exact even for >50-tx addresses.
- Pull-to-refresh now clears only the load-bearing caches for the current
  wallet (per-address caches + discovery metadata) instead of also wiping
  the global transaction-body cache, other wallets' data, and a list of
  malformed cache keys. Confirmed tx bodies are immutable and stay.
- Cold-start cache wipe now triggers only on app-version change; the old
  any->5-min-gap wipe was redundant because every affected cache already
  self-expires within 5 minutes (FRESH_LAUNCH_THRESHOLD_MS removed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
- use-tab-animation rewritten on shared values (also fixes the entrance
  replaying on every re-render while focused, since the focus effect is
  now properly memoized); the four tab screens wrap content in
  Reanimated's Animated.View
- Receive screen's refresh spin uses withRepeat/withTiming
- SplashScreen sequence rebuilt on withSpring/withTiming/withDelay with
  deterministic JS-side completion timing
- PriceChart tooltip fades via a shared value; PanResponder unchanged

The app now runs a single animation system (Reanimated); no component
imports the legacy react-native Animated API anymore.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
- New components/PressableOpacity.tsx: drop-in Pressable replacement that
  mirrors TouchableOpacity's activeOpacity feedback (repo skill 9.3)
- All 32 files with plain TouchableOpacity usages converted mechanically;
  the only remaining TouchableOpacity references are the three
  createAnimatedComponent(TouchableOpacity) bases (settings logout,
  WalletCard, TransactionItem) which already have custom Reanimated
  press feedback
- AppButton adopted for the fee-bump Bump Fee CTA (with proper loading
  state), recovery-phrase Reveal button, and biometric-setup Continue
  button; their bespoke button styles removed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
- REVAMP_NOTES: store architecture (narrow hooks preferred, useWallet
  compat), revised pipeline/refresh/cold-start behavior, Reanimated-only
  animation rule, PressableOpacity convention; deferred list pruned to
  what actually remains (useWallet stage-3 call sites, txs pagination
  gap, expo-image)
- SKILL_MEMORY entries for react-native-skills and frontend-design

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
All remaining screens and components (fee-bump, fee/wallet settings,
transaction details/explorer/history, addresses, manage-wallets,
wallet-setup, recovery phrase, xpub, pin-setup, address-details,
LiquidGlassView, FeedbackPopup, PriceChart) now subscribe through the
narrow context hooks. LiquidGlassView is notable: it wraps cards across
home and settings and was still re-rendering on every poll via the
all-slices hook.

useWallet() and the WalletStore type are deleted - the narrow hooks in
hooks/wallet-contexts.tsx are now the only store API, so new code cannot
silently reintroduce app-wide re-render fanout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
Addresses with more than ~25 confirmed transactions previously showed a
truncated list: /address/{addr}/txs returns one page and the app never
walked /txs/chain/{last_seen_txid} continuation pages.

- New getAddressTransactionsPaginated in esplora-service: fetches page 1
  in a new raw mode (no cached-body merge - the continuation cursor must
  come from the actual API page, and raw requests get their own dedupe
  key so a concurrent merged request can't corrupt it), then walks up to
  MAX_TX_CHAIN_PAGES_PER_ADDRESS chain pages sequentially through the
  rate-limited queue
- Efficiency contract: a fresh combined txid-list cache serves with zero
  requests; after TTL expiry only page 1 plus at most one chain page is
  fetched before the early-stop (a full page of already-cached txs means
  deeper history was covered by a previous scan, since confirmed history
  only prepends); deeper history is reassembled from the permanent
  confirmed-body cache via a TTL-ignoring txid-list read
- Chain pages cache their tx bodies permanently and the immutable page
  for the session; mid-pagination failures return a partial result
  (strictly better than the old always-truncated behavior)
- getWalletData's per-address loop and address-details use the paginated
  fetch; discovery and the addresses screen stay single-page by design
- Merged wallet list cap raised from 50 to 300 (history is virtualized)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
- REVAMP_NOTES: narrow hooks are the only store API; paginated history
  contract (raw page-1 mode, early-stop, display limit); deferred list
  is down to expo-image only
- SKILL_MEMORY entry on cursor-vs-cache-merge pagination learning

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
The per-address loop uses getAddressTransactionsPaginated now; the plain
getAddressTransactions import was left over and flagged by code review.
getAddressTransactions stays exported from esplora-service (still used by
address-details and the test script).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
Feature-level revamp release. Updates all version surfaces per the
project checklist: app.json, package.json, android versionName/versionCode
(1->2), iOS CFBundleShortVersionString/CFBundleVersion (1->2), README
badge, bug-report template placeholder, the About screen 'What's New'
section, and a 1.3.0 CHANGELOG entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
The Reanimated rewrite of useTabAnimation kept the tabIndex parameter
for signature compatibility but never used it. Drop the parameter and
the corresponding call-site arguments so the hook has no dead surface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iiPpE9v7J8Ja9kEx8YG2K
…t-2xio0a

Visual + performance revamp: theming, store split, pipeline, pagination
@jamespepper81 jamespepper81 merged commit 405cb29 into main Jul 12, 2026
13 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