Skip to content

Bottom-sheet primitive + TokenPicker/receive migrations (polish wave C)#34

Merged
epicexcelsior merged 11 commits into
anonmesh:v3from
epicexcelsior:epic/bottom-sheet-primitive
May 10, 2026
Merged

Bottom-sheet primitive + TokenPicker/receive migrations (polish wave C)#34
epicexcelsior merged 11 commits into
anonmesh:v3from
epicexcelsior:epic/bottom-sheet-primitive

Conversation

@epicexcelsior
Copy link
Copy Markdown
Collaborator

@epicexcelsior epicexcelsior commented May 8, 2026

Summary

Adds an AppBottomSheet primitive built on react-native-gesture-handler + react-native-reanimated (both already linked) and migrates the first two H-lane surfaces onto it. Apple-feel pan-from-anywhere, native-thread translateY, flick-to-dismiss with velocity threshold.

Note on engine choice: an earlier draft of this PR adopted @gorhom/bottom-sheet v5. That added a heavy dependency (snap-point engine, scrollable wrappers, BottomSheetModalProvider plumbing) for behavior we already had via raw RNGH + Reanimated. Walked back in 84dfd6e — the primitive now wraps a native RN Modal directly. Same Apple feel, no new package. PR title/body originally claimed gorhom; updated to match what's actually shipped.

Supersedes #33 — that PR deduped onto a weaker handle-only primitive; this lays the foundation for an honest standardization.

Files

New

  • mobile_app/components/primitives/BottomSheet.tsx — exports AppBottomSheet (the full sheet) and BottomSheetHandleBar (standalone grab indicator for route-style modals like the receive screen). Built on RN Modal + RNGH Gesture.Pan() + Reanimated useSharedValue / withTiming / withSpring. Single source of truth for app sheet behavior — pan from anywhere, native-thread 1:1 finger tracking, dismiss past 120pt distance OR 800px/s velocity, spring snap-back on partial drag, backdrop opacity interpolated against drag progress (clamped). Internal mounted state lags visible prop on close so the slide-down completes before unmount.
  • mobile_app/components/primitives/index.ts — surfaces the new exports.

Migrated

  • mobile_app/components/send/TokenPicker.tsx — drops ~120 LOC of bespoke gesture/animation scaffolding, now renders inside AppBottomSheet. Same UX but pan-from-anywhere (was handle-only).
  • mobile_app/app/receive.tsx — was using legacy PanResponder (JS-thread, laggy on Seeker). Replaced with Gesture.Pan() + Reanimated worklet writes — UI-thread translateY updates so the sheet follows the finger 1:1. Velocity threshold corrected (was 0.8 PanResponder dy/ms units → 800 gesture-handler px/s). Receive stays a Stack.Screen route; a future PR can convert it to a true sheet mounted from the wallet bento.
  • mobile_app/app/_layout.tsx — receive route switched to presentation: 'transparentModal' + animation: 'none' so the screen owns its own enter/exit animation. Eliminates the empty-container black flash on dismiss.

Why split TokenPicker (sheet) and receive (route + gesture)

Receive is mounted as a Stack.Screen with route-level presentation in app/_layout.tsx; converting it to a child sheet of the wallet bento would mean re-plumbing every router.push('/receive') caller. Out of scope. The minimum-viable Apple-feel fix is the gesture-thread upgrade alone.

TokenPicker is already a declarative visible-prop sheet, so it migrates cleanly.

Drive-by

Carries the same fix(nodes): drop stale H_PAD reference cherry-pick the other open wallet-lane PRs do — tsc --noEmit errors on upstream/v3 without it. No-ops on whichever PR merges first.

Validation

  • npx tsc --noEmit clean
  • npm run lint clean
  • npm run validate:tier0:services pass
  • node ./scripts/validate-tier0-config.mjs pass

Test plan

TokenPicker (/send/recipient → tap token chip)

  • Sheet slides up smoothly
  • Drag down from anywhere on the sheet (not just the handle) → sheet follows finger 1:1, no JS lag
  • Drag past ~120pt or flick down → dismiss
  • Drag short and release → snaps back with spring
  • Tap a token → select fires, haptic, sheet dismisses
  • Tap close icon → dismiss
  • Tap backdrop → dismiss

Receive route (wallet → Receive action)

  • Drag down on the receive screen → content follows finger smoothly (no PanResponder lag)
  • Drag past threshold → slide-down animation plays then router.back()
  • Drag short → spring back
  • Header X button plays slide-down animation (parity with gesture path; previously skipped animation and showed a black flash)
  • Inputs (REQUEST amount field) still focusable
  • Address-mode segmented control still tappable
  • Copy / Share buttons still fire

Future work (not this PR)

Apply AppBottomSheet to:

Each is a small, scoped follow-up PR using the primitive.

PR anonmesh#29 removed the H_PAD constant when moving PendingCosigns into
WalletScreen's grid (parent now owns horizontal padding) but left one
reference at line 57. Result: upstream/v3 fails tsc on a fresh clone.

Drop the stale paddingHorizontal entry from the wrap View style array
and consolidate the two duplicate @expo/vector-icons imports while in
the file.
…tive

Add @gorhom/bottom-sheet@^5 as the canonical bottom-sheet engine.
Wraps it as components/primitives/BottomSheet.tsx exposing our visual
chrome (handle indicator, glass surface, primary border accent,
backdrop tied to drag progress) with a declarative visible/onClose
API consumers already know.

What gorhom buys:
- Pan-from-anywhere on the sheet (Apple convention; not handle-only)
- Native-thread gesture + animation (no JS lag under load)
- Scroll-to-dismiss handoff for sheets with internal scroll
  (BottomSheetScrollView/FlatList re-exported from this module)
- Flick-to-dismiss with velocity projection
- Snap points (omit prop = dynamic content-fit sizing)
- Keyboard-interactive layout for sheets with TextInput
- Modal portal — render anywhere in the tree, sheet floats above

Wired BottomSheetModalProvider inside GestureHandlerRootView in the
root layout so every screen can present sheets without local provider.

BottomSheetHandleBar exported as a standalone bar for surfaces that
can't move to AppBottomSheet yet (route-style modals like the receive
screen). Visual matches the gorhom handle indicator.
Two surfaces migrate off bespoke gesture/animation scaffolding onto the
canonical primitives in this PR:

TokenPicker (components/send/TokenPicker.tsx):
- Wraps content in <AppBottomSheet visible onClose>; drops Modal,
  GestureHandlerRootView, GestureDetector, Animated.View, Pressable
  backdrop, in-line grab bar, panGesture, dismiss callback,
  DISMISS_DISTANCE/DISMISS_VELOCITY constants, sheetStyle/translateY,
  useGlass.
- Result: -120 LOC; Apple-feel pan-from-anywhere, scroll-handoff if it
  ever grows scrollable content, keyboard-interactive for free.

Receive route (app/receive.tsx):
- Was using legacy PanResponder (JS-thread → laggy under load on
  Seeker). Replaced with react-native-gesture-handler Pan +
  Reanimated worklet writes — translateY now updates on the UI thread
  so the sheet follows the finger 1:1.
- Visual handle swapped to <BottomSheetHandleBar/> from the primitive
  module so the receive route matches the gorhom-rendered indicator
  used elsewhere.
- Velocity threshold corrected (was 0.8 in PanResponder dy/ms units;
  gesture-handler uses px/s — now 800 to match other surfaces).
- Inline GrabHandle helper deleted.
- Receive stays a Stack.Screen route (presentation: 'modal') for now;
  conversion to a true gorhom BottomSheetModal mounted from the
  wallet bento is a future refactor.
Dynamic content sizing in @gorhom/bottom-sheet v5 has a measurement
race on first present — BottomSheetView reports 0 height before
children mount, the modal opens at height 0 then never measures again.
TokenPicker presented but rendered invisible.

Lock to a single 85% snap point by default with enableDynamicSizing
disabled. Consumers can pass snapPoint='60%' etc. for a different
height. index={0} explicit so present() always opens to the first
(and only, by default) snap.

Future work: revisit dynamic sizing with onLayout callback once we
have a sheet that genuinely needs content-fit height (TxDetailModal
post-merge — variable content based on tx fields).
Gorhom's BottomSheetModal was firing present() but rendering invisible
in our setup — likely a v5 dynamic-sizing/portal interaction with
Reanimated 4 + expo-router. Couldn't reproduce the working state from
the docs in our tree. Pivoting to a primitive built on
react-native-gesture-handler + Reanimated directly so we control every
piece and can debug end-to-end.

What the new AppBottomSheet does:
- Native RN Modal as the platform overlay (transparent + animationType=none).
- gesture-handler Pan with activeOffsetY([12, screenHeight]) so the
  sheet only claims the gesture on downward intent past 12pt — inner
  Pressables/inputs still receive their taps.
- Reanimated worklet writes translateY directly during drag so the
  sheet follows the finger 1:1 on the UI thread (no JS lag).
- Spring physics damping 22 / stiffness 250 (open) and 320 (back) for
  a snappy Apple-adjacent feel.
- Velocity-aware dismiss: drag past 120pt or fling > 800px/s closes;
  short release springs back.
- Backdrop opacity interpolates 0 → 0.7 with translateY progress so the
  dim fades naturally as the sheet leaves.
- Internal mounted state lags the visible prop on close so the slide-
  down animation finishes before the Modal unmounts (without it the
  sheet would just snap off-screen).
- Split dismiss-pattern: dismiss-Pressable is flex:1 above the sheet
  (no overlap with sheet area), backdrop dim is a separate
  pointerEvents:none Animated.View. Same fix pattern as TxDetailModal
  post-debug — avoids absoluteFill-sibling responder claims that
  silently swallow nested DepthButton/Pressable taps.

Removed BottomSheetModalProvider from app/_layout.tsx since it's no
longer needed (and may have been interfering with the wallet-screen
QR button's native Modal — to be verified on rebuild).

Removed @gorhom/bottom-sheet from package.json. Removed the
BottomSheetScrollView/BottomSheetFlatList re-exports from the
primitives index since they were gorhom-specific. AppBottomSheet
+ BottomSheetHandleBar remain — same public API as before.
Two bugs from the previous primitive build:

1. activeOffsetY([12, SCREEN_HEIGHT]) — array form means "activate
   when Y translation is OUTSIDE this range", i.e. < 12 OR > screen
   height. The < 12 case fires on UPWARD pan, not downward — exactly
   the opposite of what a pull-down dismiss needs. Switched to
   activeOffsetY(12) (single positive number = downward past 12pt
   activates).

2. withSpring(0, { damping: 22, stiffness: 250 }) over SCREEN_HEIGHT
   travel was underdamped (ratio ~0.7) so the sheet overshot past
   the rest position and oscillated — "shoots way up, slow." Apple's
   modal sheet open isn't a spring; it's a timing curve. Switched
   open + close to withTiming with Easing.out/in cubic (320ms / 220ms).
   Spring kept only for snap-back after a partial interactive drag —
   short distance there, no overshoot risk, feels natural.

Receive screen still uses single-number activeOffsetY (was already
correct), so it gestures down. Lag user reported there is the native
modal back-animation conflicting with our local dismiss animation —
deferring that fix to a follow-up since it requires changing the
route's presentation config.
…blue line

Two more bugs from device smoke:

1. TokenPicker still couldn't gesture down even with activeOffsetY(12)
   because RN Modal renders contents in a SEPARATE NATIVE VIEW TREE
   (Window/Dialog on Android, separate UIWindow on iOS). The app-root
   GestureHandlerRootView at app/_layout.tsx does NOT propagate into
   that tree, so GestureDetector inside the modal saw no gestures.
   Documented in react-native-gesture-handler README under "Using
   inside Modal". Added a GestureHandlerRootView wrapping the Modal
   contents — gesture context now lives inside the modal's native
   tree where children can find it.

   Receive screen worked because it's a Stack.Screen route (same
   gesture-handler context as the rest of the app), not an RN Modal.

2. "Weird blue line at top" of the sheet was borderTopWidth: 1 with
   borderColor defaulting to colors.borderStrong, which is
   rgba(0,229,255,0.28) — cyan. Switched default to colors.border
   (subtle) and dropped width to StyleSheet.hairlineWidth so the
   divider is barely visible, not a feature accent.

Also tightened the docstring to reflect the actual behavior (timing
for open/close, spring only for snap-back) and pulled the
GestureHandlerRootView wrap requirement up as critical wiring so
future maintainers see it before they break it.

Standard primitive: every new sheet in the app should use AppBottomSheet.
No per-screen ad-hoc gesture/animation rewrites.
Two changes targeting the 'tiny bit of lag/jitter' user reported on
the receive screen pan-to-dismiss:

1. Sequence the dismiss animations. Previously: pan releases past
   threshold → translateY = withTiming(800) AND router.back() fire
   simultaneously. Our local timing animates inner content down to
   800px while the native back-animation slides the WHOLE screen
   from full position to off-screen — two transforms compound into
   visible jitter.
   Now: withTiming completion callback fires router.back. By the
   time native back kicks in, our inner is already off-screen, so
   the native animation runs invisibly. One animation per frame.

2. renderToHardwareTextureAndroid + collapsable={false} on the outer
   Animated.View. The receive screen has a non-trivial subtree
   (QR svg, segmented control, two action buttons, an input). On
   Android, transforming that subtree per-frame asks the GPU to
   recomposite the whole layer. The hardware-texture hint caches
   the view as a GPU texture so per-frame translateY is cheap.
   collapsable={false} prevents Android's view-tree optimizer from
   collapsing the wrapper layer (which would defeat the texture
   cache).

No behavior change — same dismiss threshold (120pt or 800px/s),
same spring snap-back. Just smoother on Android.
User flagged: after our pan-to-dismiss slides receive content off-
screen smoothly, a black screen still slid down behind it. Cause: the
Stack-screen route container has a default dark background. After our
inner Animated.View timing reached 800px, the container itself was
still full-screen with its own (dark) background. router.back then
fired native back animation — sliding the empty dark container down
over the wallet screen. Visual: extra black flash.

Switch presentation from 'modal' to 'transparentModal'. Container
background is transparent → after our inner content slides off, the
empty container is invisible → native back animation runs over a
transparent layer → user sees wallet screen behind during back, no
black flash. Receive content keeps its own backgroundColor, so the
visible area looks identical during entry and use.
User reported a 'little black box at bottom that waits' after swipe-
down dismiss, even with transparentModal. Cause: the native back
animation was still slid the empty Stack-screen container down behind
our animation, and any height mismatch (window-height translate vs
screen-height container) left a sliver of content visible at the
bottom edge.

Solid architecture: receive owns ALL motion via Reanimated; the route
itself runs zero native animations.

Three changes:

1. app/_layout.tsx receive Stack.Screen options:
   - presentation: 'modal' → 'transparentModal'  (transparent container)
   - animation: 'slide_from_bottom' → 'none'    (no native enter/exit)
   - contentStyle: { backgroundColor: 'transparent' }  (the screen's
     own content layer is also transparent)
   The route is invisible at every phase; only our own Animated.View
   has a backgroundColor and only it ever moves.

2. app/receive.tsx initialization:
   - dragY initial value: 0 → SCREEN_HEIGHT (start off-screen)
   - useEffect on mount: withTiming(0, ease-out cubic, 320ms) — slides
     the content UP from off-screen into rest position. Replaces the
     native slide_from_bottom we just disabled.
   - Dismiss timing already sequenced via callback — slides DOWN to
     SCREEN_HEIGHT then fires router.back which now unmounts instantly
     (animation:'none'), invisible to the user.

3. SCREEN_HEIGHT source: Dimensions.get('window') → Dimensions.get('screen')
   in BOTH receive.tsx AND BottomSheet.tsx primitive. 'window' excludes
   status bar; 'screen' is the full device extent. For Android edge-to-
   edge content under the gesture nav, 'window' translate left a
   sliver below the safe area = the 'little black box that waits'.

Result: from the user's POV, taps Receive → screen slides up, can pan
freely, swipes down → screen slides down, gone. No native back
animation, no empty container, no black flash, no sliver. Single
animation source per surface — receive owns its frame, BottomSheet
primitive owns its frame, identical curves and timings.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Introduces a new AppBottomSheet primitive and migrates the TokenPicker and Receive modal interactions to a gesture-driven, Reanimated-based presentation to improve drag-to-dismiss smoothness and consistency.

Changes:

  • Added components/primitives/BottomSheet.tsx exporting AppBottomSheet and BottomSheetHandleBar, and surfaced them via components/primitives/index.ts.
  • Migrated components/send/TokenPicker.tsx to render within AppBottomSheet (removing its bespoke Modal + gesture/animation scaffolding).
  • Updated the /receive route to use gesture-handler + Reanimated timing for enter/exit, and adjusted stack options to transparentModal + animation: 'none' to avoid competing native transitions.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
mobile_app/components/send/TokenPicker.tsx Replaces custom modal/gesture sheet with AppBottomSheet usage.
mobile_app/components/primitives/index.ts Exports the new bottom-sheet primitive and handle bar.
mobile_app/components/primitives/BottomSheet.tsx Adds a reusable modal + RNGH/Reanimated bottom-sheet implementation.
mobile_app/components/nodes/PendingCosigns.tsx Consolidates icon imports and removes stale horizontal padding reference.
mobile_app/app/receive.tsx Replaces PanResponder with RNGH Gesture.Pan() + Reanimated-driven enter/exit and dismiss sequencing.
mobile_app/app/_layout.tsx Switches receive to transparentModal with animation: 'none' to rely on Receive’s custom animation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mobile_app/components/primitives/BottomSheet.tsx
Comment thread mobile_app/components/primitives/BottomSheet.tsx
Comment thread mobile_app/app/_layout.tsx
Comment thread mobile_app/app/receive.tsx Outdated
… through animated dismiss

BottomSheet.tsx
  Backdrop opacity interpolation now clamps. A fast flick can park
  translateY past SCREEN_HEIGHT before the close timing settles; without
  clamping, opacity extrapolates negative. Reanimated/RN tolerate it today
  but the contract is undefined.

receive.tsx
  Extract animateAndDismiss helper that runs the same withTiming(SCREEN_HEIGHT,
  TIMING_CLOSE) → router.back sequence the gesture's onEnd already used. The
  header X button now calls this helper instead of router.back() directly.
  Without this, header-X dismiss triggered the route's animation:'none' on a
  still-on-screen content view, producing a black flash. All non-gesture
  dismiss paths now share one animation curve.
@epicexcelsior epicexcelsior changed the title Bottom-sheet primitive (gorhom) + TokenPicker/receive migrations (polish wave C) Bottom-sheet primitive + TokenPicker/receive migrations (polish wave C) May 8, 2026
@epicexcelsior epicexcelsior requested a review from Copilot May 8, 2026 09:54
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment thread mobile_app/app/receive.tsx
Comment thread mobile_app/components/primitives/BottomSheet.tsx
@epicexcelsior epicexcelsior merged commit 3429def into anonmesh:v3 May 10, 2026
4 checks passed
epicexcelsior added a commit to epicexcelsior/anon0mesh that referenced this pull request May 11, 2026
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 added a commit that referenced this pull request May 11, 2026
…imitive gesture/dim bugs (#41)

* refactor(tx-detail): migrate TxDetailModal to AppBottomSheet

The transaction-detail modal previously used the bespoke RN Modal +
dismissArea pattern that predates the AppBottomSheet primitive landed
in #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.

* fix(bottom-sheet): leading backdrop fade + more sensitive pan activation

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 #34) and TxDetailModal (new in this PR). No
behavior changes for callers — same props, same lifecycle.

* fix(bottom-sheet): kill snap-back jitter — timing curve instead of spring

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 deleted the epic/bottom-sheet-primitive 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.

2 participants