bug(frontend): Connect Wallet modal has no obvious close control and does not dismiss on outside click #672
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:implement
agent:implement
agent:open_issues
agent:ready
agent:research
agent:security_audit
agent:verify
architecture
backend
blocker:hybrid
blocker:launch
blocker:limit-orders
blocker:v2
block:log_only
block:security
bug
ci
contracts
correctness
deploy
dev
devops
docs
documentation
duplicate
e2e
enhancement
epic
feature
frontend
functional-completion
gas
good first issue
governance
help wanted
high-risk
hooks
hybrid
indexer
infra
infrastructure
integrators
invalid
launch-blocker
limit-orders
localnet
localterra
low priority
missing-implementation
needs-design
ops
performance
priority
high
priority
medium
product
qa
QA
question
ready
ready
research
scripts
security
security-hardening
smartcontracts
tech-debt
testing
ux
UX
v2
verification
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/cl8y-dex-terraclassic#672
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
The Connect Wallet dialog is hard to dismiss. Retail users report there is no close button, and the only reliable exit they find is clicking the header — not the dimmed page, Escape, or a visible Close control.
This is a dismiss / overlay bug on the shared
Modalprimitive (Connect Wallet, WalletConnect pairing, Expert Mode). It is not a connect, signing, or quote bug. Bundle header close visibility, backdrop / outside-click, Escape, header Connect Wallet toggle, and short-viewport clipping into one issue. Do not split “add an X” vs “click outside” vs “header toggle.”Community report (anonymized): “There’s no close button for the wallet modal, and it can only be closed by clicking the header instead of anywhere else on the page.”
Related (do not regress): #187 connected-wallet menu
app-menu-dismiss+ Escape, #138 first-visit risk modal must stay non-dismissible, #554 pairingz-[10001]above Connectz-[9999]+ Cancel / timeout, #160 / #490 connect-row layout, #517 clickwrap after connect, #488 tokens.Current codebase
How the dialog opens
WalletButton.tsxlives in the sticky header (Layout.tsx). When disconnected, Connect Wallet / Connect only callssetWalletModalOpen(true)— it does not toggle closed if the dialog is already open. In-page CTAs (/portfolio, Swap / Trade / Pool / Pay “Connect Wallet”) calluseWalletStore.openWalletModal().WalletModal.tsxportals through sharedModal.tsx(title="Connect Wallet",rootTestId="wallet-connect-modal-portal"). Same primitive:WalletConnectPairingModal.tsx(z-[10001]),ExpertModeModal.tsx.RiskAcknowledgementModal.tsxusesdismissible={false}on purpose (#138).Close controls that already exist (but fail the report)
Modal.tsxwhendismissible(defaulttrue):aria-label="Close modal"+ SVG, classbtn-muted !min-h-0 !px-2.5 !py-2docs/qa/issue-488/wallet-modal-dark.jpg) show the X, but it reads as header chrome, not a dismiss CTA..app-modal-backdrop(position: absolute; inset: 0)onClick→onClosefixed inset-0 flex items-center justify-center p-4and has no click handler. Clicks that hit the flex root (not the backdrop) are swallowed and do not close. No Vitest for backdrop click.documentkeydownwalletModalOpen: true.app-modal-panelisposition: relative; z-index: 10; overflow: hiddenand vertically centered. The wallet list is tall (Simulated + 5 production rows + Install). On a short phone / zoomed viewport,items-centercan push the header + X above the visible area. The panel does not pin the header or scroll the body independently. Mobile.app-modal-portal-rootonly addspadding-bottomfor the tab bar (index.css).Connected-wallet dropdown already uses a full-viewport
app-menu-dismissbutton (#187). The Connect dialog does not reuse that pattern.Close vs in-flight connect
useWallet.tscloseWalletModalandWalletModal.handleClose: ifisConnecting, they callcancelConnection()(aborts WC, closes pairing, setswalletModalOpen: false) and return.cancelConnectionalready clears connect state. Header Cancel while connecting (#554 WC-M9) is separate and must stay.When pairing is open,
WalletModalreturnsnullso only the pairing sheet is shown (WC-M8). Pairing has Cancel plus the same header X / backdrop.Test gaps
Modal.test.tsxcovers Escape + hiding the X whendismissible={false}. It does not assert backdrop click, visible close control, or “click panel does not close.”WalletModal.test.tsxcovers badges / logos / mobile WC rows, not dismiss.useWallet.test.tsdoes not assertcloseWalletModal/ trigger toggle.Why this is needed
Connect Wallet is the first dialog most retail users open. If they cannot find a close control and tapping the dimmed page does nothing, they assume the dApp is stuck. “Click the header” is an accidental hit on the title-row X, not a designed exit. The connected menu already dismisses on outside tap + Escape (#187); Connect should match that expectation without weakening the blocking risk ack (#138).
Constraints / guardrails
Modal, keepdismissible. Risk acknowledgement (#138) must remain non-dismissible: no X, no backdrop, no Escape, no header-trigger bypass. Expert Mode may stay dismissible (it already has Cancel).z-[10001]above Connectz-[9999]. Dismiss on pairing must stillcancelConnection()(abort pending WC, ignore late session). Do not leaveisConnectingstuck.target=_blank), pairing Open / Copy, or Expert Mode inputs must not dismiss. UsestopPropagationon the panel (or equivalent), not a document click that treats the panel as “outside.”z-indexabove the portal to “win” clicks (#181 / T527-8).max-height+ scroll the body, pin the header; do not clip the X withoverflow: hiddenon the whole panel. Keep mobile tab-bar padding; do not cover#trade-pair-select/ Settings by raising z-index.*-neo, no gold fill). Visible name:aria-labellike Close / Close connect wallet (not only a low-contrast glyph).:focus-visiblering with--focus-ring. No new lecture copy. Icon decorative if a text label is shown.dangerouslySetInnerHTML. Do not bind wallet names into the close control as HTML.Relevant files
frontend-dapp/src/components/ui/Modal.tsxdismissiblefrontend-dapp/src/index.css.app-modal-backdrop,.app-modal-panel,.app-modal-header, mobile.app-modal-portal-rootpaddingfrontend-dapp/src/components/wallet/WalletModal.tsxhandleClosevsisConnectingfrontend-dapp/src/components/wallet/WalletButton.tsxcreatePortal; no togglefrontend-dapp/src/hooks/useWallet.tsopenWalletModal/closeWalletModal/cancelConnectionfrontend-dapp/src/components/wallet/WalletConnectPairingModal.tsxfrontend-dapp/src/components/legal/RiskAcknowledgementModal.tsxdismissible={false}frontend-dapp/src/components/swap/ExpertModeModal.tsxfrontend-dapp/src/components/ui/__tests__/Modal.test.tsxfrontend-dapp/src/components/wallet/__tests__/WalletModal.test.tsxfrontend-dapp/src/components/wallet/__tests__/WalletButton.test.tsxfrontend-dapp/src/hooks/__tests__/useWallet.test.tsskills/AGENTS_FRONTEND_WALLET_CONNECT_MODAL.mdskills/AGENTS_FRONTEND_WALLETCONNECT_MOBILE.mdskills/AGENTS_FRONTEND_RISK_DISCLAIMERS.mddocs/frontend.mdRecommended direction
sm+),aria-label="Close"or Close connect wallet, contrast that holds in dark and light. Do not rely onbtn-mutedalone if it blends into--panel-bg-strong.app-menu-dismiss), andstopPropagationon the panel. Do not require users to hit only the absolutely positioned backdrop sibling. Keeprole="dialog"/aria-modal/ focus trap.walletModalOpen(close orcancelConnectionif connecting). Same for accessibility:aria-expandedif you expose a disclosure relationship.dismissible={false}unchanged, connecting dismiss cancels, header toggle. Optional Playwright: open Connect from header on/, click dimmed page, dialog gone (wallet-connect-modal-portaldetached).docs/frontend.md/ the wallet-connect-modal playbook.Acceptance criteria
isConnecting/ pairing sheet open: X, backdrop, Escape, pairing Cancel, and header Cancel all abort connect and clearisConnecting; no late WC session attaches (#554 WC-M9).z-[10001]/z-[9999]). Clickwrap after a real connect is unchanged (#517).make lint-frontendandmake test-frontendgreen. New Vitest covers D1–D4 and D7.Test plan (all paths)
Idle Connect (not connecting)
/disconnected — open from header Connect Wallet. Confirm D1 (dark + light, 375 + 1280).wallet-connect-modal-portalgone). Repeat from/portfolioConnect CTA and Swap Connect Wallet submit.In-flight connect / pairing
isConnectingfalse, pairing closed, no address set.Blocking + other modals
localStoragerisk key): no X; backdrop and Escape do nothing; Continue still requires checkbox (#138).Regression commands
No LocalTerra required for dismiss UX. Do not bind-mount
indexer/for cargo.Test plan (attack, hack, and abuse)
Dismiss wiring sits on the connect funnel. A broken overlay can trap the user, steal clicks, or look like a connected session.
pointer-eventsor a full-page dismiss button with a hole that is too small can steal Swap amount / token-picker / Settings clicks after “close,” or block them while open.z-indexabove the portal.addressor writecl8y_wallet_connection.connectAttemptId/ abort so a late session cannot attach (#554).dismissible={false}: backdrop / Escape / missing X. Header Connect does not unmount the risk dialog.stopPropagationon Install and pairing<a href>must remain so a parent dismiss does not fire on the same tap as navigation. Allowlist for WC links unchanged (WC-M5).noopener+ official store URL. Pairing href still allowlisted; blocked href does not navigate.aria-modaldialog (spoofed “page is usable”). Escape-only without an X fails WCAG on some AT.z-[10001]+ on Connect (not pairing) can hide pairing Open / Copy or cover TermsGate.z-[100]) may stay below — do not lift toasts through the dialog.namein the title must stay text.dangerouslySetInnerHTMLon header / close.isConnectingset({ walletModalOpen: false })while connecting leaves header on Cancel forever.Out of scope as attack surface: contracts, indexer, fee math.
Verification criteria
Done when a reviewer on
make devcan open Connect Wallet from the header and from an in-page CTA, see an obvious close control, and dismiss by (1) that control, (2) clicking the dimmed page, (3) Escape, and (4) clicking header Connect Wallet again — at ~375px and ~1280px, dark and light — and an in-flight WalletConnect cancel still clears connecting state. Risk ack stays blocking.Not done if the only exit is still the title-row hit box; if outside clicks hit the portal root and do nothing; if the X is off-screen on a phone; or if risk ack / pairing stack / clickwrap regress.
Evidence: before/after crops of the Connect header (dark + light, 375 + 1280) and a note that backdrop click was exercised. No user identity, addresses, or mnemonics in screenshots or issue comments.
marked as related to #187
marked as related to #138
marked as related to #554
marked as related to #160
mentioned in commit
1ba8c8cf7cmentioned in merge request !1160
Implemented on
fix/672-wallet-modal-dismiss— MR !1160.Acceptance (this MR):
max-height.stopPropagation).closeWalletModal(aria-expanded).cancelConnection()(header returns to Connect Wallet).dismissible={false}.z-[10001]unchanged; dismiss without connect does not write wallet storage.make lint-frontend+make verify-issue-672green.Manual browser (worktree Vite): Close, backdrop, Escape, Galaxy Station row stays then Close-while-connecting. Did not attach screenshots.
Left for reviewer: 375×667 / light theme visual, Playwright
VERIFY_ISSUE_672_CHAIN=1, first-visit risk ack with empty localStorage, clickwrap after a real connect.mentioned in commit
bd868e228ementioned in commit
c7b888ff56Merged to
mainvia !1160. Wallet dropdown tests from #671 were kept alongside Close.Leftover: Connect Wallet modal still has a labeled dismissible Close after frontend rebuild.
make verify-issue-672.mentioned in issue #671
mentioned in issue #673