refactor(tx-detail): migrate TxDetailModal to AppBottomSheet + fix primitive gesture/dim bugs#41
Merged
epicexcelsior merged 3 commits intoMay 11, 2026
Conversation
The transaction-detail modal previously used the bespoke RN Modal + dismissArea pattern that predates the AppBottomSheet primitive landed in anonmesh#34. The recipe in BottomSheet.tsx's docstring lists this surface as a planned consumer; this PR makes the swap. Behavior changes for users: - Pull-to-dismiss gesture (1:1 finger tracking on the UI thread) with velocity-based release. - Backdrop dim now fades smoothly with drag progress instead of the binary slide-in/out of the previous implementation. - Apple-modal-curve open/close timing. The modal's contents are unchanged: the same header with status pill, the same six DetailRow entries, the same Explorer + Close action pair. Existing inner ScrollView is dropped; AppBottomSheet's maxHeight 90% is sufficient for the detail panel's natural height across all real-world variants (memo + mint extras included). This is the second consumer of AppBottomSheet after TokenPicker, strengthening the primitive as the single source of truth for sheet-style surfaces.
Two bugs uncovered during Seeker smoke of the TxDetailModal migration: 1. Backdrop "slides up" with the sheet on open. Root cause: backdrop opacity was interpolated directly from `translateY`, so the dim cross-faded synchronously with the slide. Visually that reads as "dim rising with the sheet" rather than the iOS-standard "sheet rises onto already-dim background". Fix: give the backdrop its own `backdropOpacity` shared value. On open, animate it over 200ms with no delay (leading the 320ms sheet slide). On close, animate both in lockstep over 220ms. During interactive drag, the Pan's onUpdate mirrors translateY to backdropOpacity so dismissal still feels coupled to the gesture. 2. Pan-from-body unresponsive. Root cause: `activeOffsetY(12)` is too permissive a window for Pressable children to compete in via RN's responder system. Press claims the touch, drags through the first ~10pt before Pressable cancels (movement > pressRectOffset), and by then gesture-handler often can't pick up the in-progress drag cleanly. No `activeOffsetX` constraint also meant diagonal flicks could ambiguously route between Pan and child taps. Fix: lower `activeOffsetY` 12 → 5 (claim before Pressable's cancel window), add `activeOffsetX([-20, 20])` to constrain to vertical motion, and add `shouldCancelWhenOutside(false)` so a drag that crosses the sheet bounds during interaction doesn't get cancelled. Affects every consumer of AppBottomSheet: TokenPicker (already on upstream/v3 from PR anonmesh#34) and TxDetailModal (new in this PR). No behavior changes for callers — same props, same lifecycle.
…ring Snap-back after a partial drag previously used a spring (damping 22, stiffness 320). That spring is mathematically underdamped — damping ratio ≈ 0.61 against critical 35.78 — so the system overshoots the rest position and oscillates on settle. After short drags (e.g. user pulls down 20pt then releases without dismissing) there's no perceptual cover for the wobble, and it reads as jitter. Replace with a deterministic timing curve (260ms ease-out exponential) for both translateY and backdropOpacity. No overshoot, no oscillation, no visible jitter on settle. This is what production sheet libraries (gorhom/bottom-sheet, etc.) use for snap-back for exactly this reason. Affects every consumer of AppBottomSheet — TokenPicker and TxDetailModal (this PR). Same props, same lifecycle.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two coupled changes:
TxDetailModal.tsxmigrated from the bespoke RN Modal + dismissArea pattern to theAppBottomSheetprimitive (planned consumer perBottomSheet.tsxdocstring). One less ad-hoc sheet in the codebase.BottomSheet.tsxprimitive fixes for two bugs uncovered during Seeker smoke of ADD: Arcium Escrow Confidential program #1:translateYshared value as the sheet position, so they cross-faded in lockstep. Fix: give backdrop its own shared value with a leading 200ms fade-in (vs the sheet's 320ms slide). iOS-standard "dim establishes first, sheet rises onto already-dim background" feel.activeOffsetY(12)left too wide a window for Pressable children to claim the touch via RN's responder system before the Pan could activate. Fix: loweractiveOffsetY12 → 5 (claim before Pressable's press-cancel window at ~10pt), addactiveOffsetX([-20, 20])to constrain to vertical-only motion, addshouldCancelWhenOutside(false)so a drag crossing sheet bounds doesn't get cancelled.Files
components/wallet/TxDetailModal.tsx— drop outer Modal/dismissArea/sheet wrappers + inner ScrollView, swap for<AppBottomSheet>. Drop bespoke handle (primitive renders its own). Drop unusedSafeAreaViewimport.components/primitives/BottomSheet.tsx— backdrop decoupled to its ownbackdropOpacityshared value; Pan thresholds tuned more aggressively for Android Pressable competition. Docstring updated.Primitive fixes affect every consumer: TokenPicker (already on
upstream/v3from #34) and TxDetailModal (new in this PR). No behavior changes for callers — same props, same lifecycle, same hook signatures.Net diff: roughly +110 / -130 across the two files.
Test plan
TxDetailModal (the new consumer):
TokenPicker regression check (existing consumer):
Build:
npx tsc --noEmitcleannpm run lintclean for both touched filesHistory
Branch has 2 commits — initial TxDetailModal migration + primitive bugfix. Squash on merge.