From 22aee51dfe79f4ea8a9ad4b6f21d020e8b0017ff Mon Sep 17 00:00:00 2001 From: dcccrypto Date: Thu, 3 Sep 2026 10:28:07 +0100 Subject: [PATCH] fix(#2286): close the two remaining modal divergences; primitive still open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The inventory in #2286 is substantially out of date, so this fixes what actually diverges rather than what was reported. MEASURED ACROSS ALL FIVE TRADE MODALS: z-index all five z-[9999] — CONVERGED (issue reported z-50, z-[60], z-[61]) portal all five createPortal — CONVERGED Escape all five — CONVERGED reduced motion all five — CONVERGED scroll lock 4 of 5 — FundingExplainerModal missing <-- fixed scrim 4x bg-black/80, 1 outlier — SendPositionNftModal <-- fixed focus trap ZERO of five — still open, see below Two fixes: * FundingExplainerModal had no `useLockBodyScroll`, so the page scrolled behind it while the other four locked. That is a real bug, not just inconsistency. * SendPositionNftModal's `bg-black/60 backdrop-blur-[2px]` is now bg-black/80, matching the other four. WHAT I DID NOT DO, and why. The issue asks for a shared primitive and a migration of all five. I did not ship that, for two reasons that compound: 1. These five components have ZERO test coverage — `grep -rl __tests__` returns nothing for every one of them. 2. PLAYGROUND.md's definition of done requires clicking through the changed flow in a browser, which I cannot do here. "Types don't prove UX" is its wording and it is right. Migrating TradeConfirmationModal and ClosePositionModal — the two that move money — blind, with no tests and no visual check, is not a trade worth making for a consistency refactor. The primitive is still the correct end state; it wants a browser. ALSO CORRECTING MY OWN MEASUREMENT: my first pass reported all five as missing scroll lock and three as having a focus trap. Both were grep artifacts — `useLockBodyScroll` did not match my pattern, and `focus=7` was counting Tailwind `focus:` classes. The numbers above are from matching the actual hook names. Launch suite: 3149 passed / 16 skipped / 0 failed. Refs: dcccrypto/percolator-launch#2286 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01NgoNgagkvw7i5SSRC3FJ8D --- app/components/trade/FundingExplainerModal.tsx | 5 +++++ app/components/trade/SendPositionNftModal.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/components/trade/FundingExplainerModal.tsx b/app/components/trade/FundingExplainerModal.tsx index 2bab20a71..c5016ca43 100644 --- a/app/components/trade/FundingExplainerModal.tsx +++ b/app/components/trade/FundingExplainerModal.tsx @@ -4,6 +4,7 @@ import { FC, useEffect, useRef } from "react"; import { createPortal } from "react-dom"; import gsap from "gsap"; import { usePrefersReducedMotion } from "@/hooks/usePrefersReducedMotion"; +import { useLockBodyScroll } from "@/hooks/useLockBodyScroll"; interface FundingExplainerModalProps { onClose: () => void; @@ -13,6 +14,10 @@ export const FundingExplainerModal: FC = ({ onClose const overlayRef = useRef(null); const modalRef = useRef(null); const prefersReduced = usePrefersReducedMotion(); + // #2286: this was the ONE modal of the five without a scroll lock, so the + // page scrolled behind it. The other four already call this hook; the + // divergence the issue describes is now down to this and the scrim. + useLockBodyScroll(); // Keep the onClose callback in a ref so the mount effect never re-runs on parent // re-renders. The parent FundingRateCard runs a 1-second `setCountdown` setInterval, diff --git a/app/components/trade/SendPositionNftModal.tsx b/app/components/trade/SendPositionNftModal.tsx index c95d4a07b..b1f9f7ed3 100644 --- a/app/components/trade/SendPositionNftModal.tsx +++ b/app/components/trade/SendPositionNftModal.tsx @@ -122,7 +122,7 @@ export const SendPositionNftModal: FC = ({ onClick={(e) => { if (e.target === e.currentTarget && !loading) onCancel(); }} - className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/60 backdrop-blur-[2px] p-4" + className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/80 p-4" aria-modal="true" role="dialog" aria-label="Send Position NFT"