Skip to content

refactor(tx-detail): migrate TxDetailModal to AppBottomSheet + fix primitive gesture/dim bugs#41

Merged
epicexcelsior merged 3 commits into
anonmesh:v3from
epicexcelsior:epic/tx-explorer-button
May 11, 2026
Merged

refactor(tx-detail): migrate TxDetailModal to AppBottomSheet + fix primitive gesture/dim bugs#41
epicexcelsior merged 3 commits into
anonmesh:v3from
epicexcelsior:epic/tx-explorer-button

Conversation

@epicexcelsior
Copy link
Copy Markdown
Collaborator

@epicexcelsior epicexcelsior commented May 11, 2026

Summary

Two coupled changes:

  1. TxDetailModal.tsx migrated from the bespoke RN Modal + dismissArea pattern to the AppBottomSheet primitive (planned consumer per BottomSheet.tsx docstring). One less ad-hoc sheet in the codebase.
  2. BottomSheet.tsx primitive fixes for two bugs uncovered during Seeker smoke of ADD: Arcium Escrow Confidential program #1:
    • Backdrop dim "slides up" with the sheet on open — it was opacity-interpolated from the same translateY shared 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.
    • Pan-from-body unresponsive — 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: lower activeOffsetY 12 → 5 (claim before Pressable's press-cancel window at ~10pt), add activeOffsetX([-20, 20]) to constrain to vertical-only motion, add shouldCancelWhenOutside(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 unused SafeAreaView import.
  • components/primitives/BottomSheet.tsx — backdrop decoupled to its own backdropOpacity shared value; Pan thresholds tuned more aggressively for Android Pressable competition. Docstring updated.

Primitive fixes affect every consumer: TokenPicker (already on upstream/v3 from #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):

  • Wallet → tap any recent-activity row → modal slides up over fully-established dim background (dim should NOT appear to "slide up with" the sheet)
  • Pull down on sheet body (anywhere, not just handle) past ~120pt → dismisses with momentum
  • Pull down + release before threshold → snaps back to rest, backdrop dim restores
  • Flick down with high velocity → dismisses regardless of distance
  • Tap copy on Signature / Counterparty / Memo / Mint → check icon flips, value flashes primary color (pull-to-dismiss should NOT trigger on tap-and-release)
  • Tap "Explorer" → opens devnet Solana Explorer
  • Tap "Close" → modal dismisses
  • Tap dismiss area above sheet → modal dismisses (existing tap-outside path still works)
  • Test with both a SOL tx (4 detail rows) and an SPL tx (6 detail rows with memo + mint)

TokenPicker regression check (existing consumer):

  • Wallet → Send → "Choose token" sheet → pull-to-dismiss now works (it should — the primitive fix benefits this surface too)
  • Tap a token row → selects token, sheet closes (no spurious pan triggered by tap)
  • Backdrop dim no longer reads as sliding up with the sheet on open

Build:

  • npx tsc --noEmit clean
  • npm run lint clean for both touched files

History

Branch has 2 commits — initial TxDetailModal migration + primitive bugfix. Squash on merge.

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.
@epicexcelsior epicexcelsior changed the title refactor(tx-detail): migrate TxDetailModal to AppBottomSheet refactor(tx-detail): migrate TxDetailModal to AppBottomSheet + fix primitive gesture/dim bugs May 11, 2026
…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.
@epicexcelsior epicexcelsior marked this pull request as ready for review May 11, 2026 17:14
@epicexcelsior epicexcelsior merged commit 44ece0e into anonmesh:v3 May 11, 2026
@epicexcelsior epicexcelsior deleted the epic/tx-explorer-button branch May 15, 2026 09:09
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.

1 participant