bug(frontend): Swap direction button does not mask the Pay/Receive seam line #659
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#659
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
On Swap (
/), the switch-directions control sits on the You Pay / You Receive seam, but the hairline divider still draws through the button and its arrows. The control looks unfinished: a faint grey line crosses the blue up/down icon.This is a paint / stacking bug, not a quote or execute bug. Flip still swaps pay/receive tokens. Same surface is used for wrap/unwrap quotes on Swap (Playwright wrap-swap E5). Bundle dark + light, phone + desktop, idle + hover + keyboard focus into one issue. Do not split “opaque fill” vs “hover gap” vs “focus ring.”
Community report (anonymized): Swap page, dark theme — “the horizontal line going through the switch directions button is ugly.”
Related (do not regress): #488 design tokens, #653 one chrome layer (Swap IO cards stay allowlisted), #144 / #181 focus-visible, #489 copy density, #415
*-glassrename.Current codebase
Layout (intentional seam)
SwapPage.tsxbuilds one.swap-io-stackinside the Swapshell-panel-strong:card-glass swap-io-card-payrelative z-20 … -my-5 sm:-my-[22px](negative margin pulls the control onto the seam;pointer-events-noneon the wrapper,pointer-events-autoon the button)card-glass swap-io-card-receiveThe button is
aria-label="Swap pay and receive tokens", classswap-direction-btn, and swapsfromToken/toToken. Icon is an inline SVG (vertical arrows). Hover uses Tailwindhover:-translate-y-0.5.There is no second Swap IO stack. Wrap/unwrap uses this same page.
/tradeBid↔Ask and.limit-side-flip-btnare different controls and are out of scope unless a copy-paste would reintroduce a transparent plate on a seam.CSS (why the line shows through)
index.css(comment: “two halves abut; direction button sits on the seam”):.swap-io-stackisolation: isolate,overflow: hidden, 24px radius,--chrome-border,--shadow-card, blur.swap-io-stack::before--io-stack-bgatz-index: 0.swap-io-card-pay.card-glass/.swap-io-card-receive.card-glassz-index: 1, transparent fill (stack paint shows through), no own shadow.swap-io-card-pay.card-glassborder-bottom: 1px solid var(--chrome-border)— this is the horizontal line.swap-io-card-receive.card-glassmargin-top: -1px(abut).swap-direction-btnbackground: var(--control-surface),--shadow-card.swap-direction-btn:hoverbackground: var(--control-surface-hover)Dark
theme-dark.css/:rootinindex.css:--control-surface: rgba(255, 255, 255, 0.035)and hover0.06. That is a glass wash, not a plate. The button is alreadyz-20above the cards; the seam still shows through the fill. Lighttheme-light.cssuses--control-surface: rgba(255, 255, 255, 0.72)— less severe, but the hairline can still read through the rounded plate, especially on hover translate.--io-stack-bgmid stops (the seam band) are roughlyrgba(22, 28, 42)/rgba(16, 21, 32)in dark andrgba(248, 250, 252)/rgba(241, 245, 249)in light.Gaps
:focus-visiblerule on.swap-direction-btn(contrast.limit-side-flip-btn:focus-visiblewhich stacks--focus-ring). Keyboard Tab to the flip control has no dedicated ring.-translate-y-0.5moves the plate off the seam. Even after an opaque fill, the vacated 2px can re-expose the hairline unless a static mask / underlay stays on the seam..swap-io-stack { overflow: hidden }will clip any halo or translate that extends outside the rounded stack.e2e/swap.spec.ts,e2e/wrap-swap.spec.tsE5). No paint / computed-background assertion.Why this is needed
Swap is the default retail surface. The flip control is the only visual join between Pay and Receive. A hairline through the icon reads as a broken layer (divider on top of the button), not as a deliberate QuickSwap-style overlap.
--control-surfaceis the right token for inset chips on a panel; it is the wrong token for a control that must occlude a 1px seam. Fixing z-index alone cannot hide a line that is visible through a 3.5% white fill.Constraints / guardrails
fromToken/toToken), quote / hybrid / wrap execute, slippage, ortoRawAmountfilters..swap-io-card-payborder-bottomand do not split the stack into two floating cards with a gap (that fights #653 C653-2 / C653-5: Swap IO cards remain the allowlisted nest).card-glass/shell-panel*around the button. Keepscripts/chrome_nesting_allowlist.txt→SwapPage.tsxas-is unless the file path moves.--io-stack-bgstop,--panel-bg, or a new theme token in boththeme-dark.cssandtheme-light.css). No hard-coded Tailwindprimary/dexhex. No gold fill. No*-neo.overflow: hidden. Do not drop stack clipping just to unclip a halo — that also clips the 24px radius. Size the plate so it stays inside the stack at 375px and 1280px (w-10/sm:w-11).pointer-events-none+ buttonpointer-events-auto. Do not raise z-index so the flip overlays token pickers, Settings, legal/risk modals, or portaled listboxes (#632). Min tap size stays ~40–44px..swap-direction-btn:focus-visibleusingvar(--focus-ring)(same mix as.limit-side-flip-btn/.swap-io-amount-input).:focus-visibleonly — not bare:focus.aria-label="Swap pay and receive tokens". No new lecture text under the flip.aria-hidden. Contrast of blue glyph on the new plate must hold in both themes. Nooutline: nonewithout a replacement ring.dangerouslySetInnerHTML. Do not bind token metadata into the button.Relevant files
frontend-dapp/src/pages/SwapPage.tsxswap-direction-btnfrontend-dapp/src/index.css.swap-io-stack*,.swap-io-card-*,.swap-direction-btnfrontend-dapp/src/theme-dark.css--control-surface,--io-stack-bg,--chrome-borderfrontend-dapp/src/theme-light.cssfrontend-dapp/e2e/swap.spec.tsfrontend-dapp/e2e/wrap-swap.spec.tsscripts/chrome_nesting_allowlist.txtSwapPage.tsxallowlistedskills/AGENTS_FRONTEND_DESIGN_SYSTEM.md*-neoskills/AGENTS_FRONTEND_CHROME_NESTING.mdcard-glassskills/AGENTS_FRONTEND_A11Y_FOCUS.md:focus-visible+--focus-ringdocs/design-system.mdRecommended direction
.swap-direction-btnan opaque fill sampled from the seam band of--io-stack-bg(solidrgb(…)mid-stop, or a dedicated--swap-direction-surfacein both theme files). Hover fill stays slightly lighter/darker but still opaque.z-indexbetween cards and the moving button) sohover:-translate-y-0.5cannot uncover the hairline. Alternatively drop the translate.--shadow-cardso the control still “sits on” the stack.:focus-visiblering (copy the.limit-side-flip-btnpattern)..swap-direction-btnresolvedbackground-coloralpha is 1 (or equivalent opaque) in bothdata-themevalues. E2E keep existence + click-to-swap pay/receive identities.Acceptance criteria
--chrome-borderhairline is visible inside the direction button or across the arrow icon (375px and 1280px).--focus-ring; mouse click does not leave a persistent ring (:focus-visibleonly).aria-labelunchanged; wrap/unwrap path on/still shows the same control.python3 scripts/check_chrome_nesting.pyandpython3 scripts/check_design_tokens.pystay green. No new*-neo. No extracard-glassnest.z-indexdoes not steal those hits.Test plan (all paths)
Visual / theme
/disconnected, dark, 375px and 1280px — screenshot the seam; line must stop at the plate.Interaction
/(same page) — E5 control still present and paintable.Regression commands
No LocalTerra required for the paint fix. Do not bind-mount
indexer/for cargo.Test plan (attack, hack, and abuse)
This change is CSS + optional static markup. Still verify it cannot be used to interfere with Swap.
z-20wrapper withpointer-events: autowould steal Pay amount / token-picker clicks.pointer-events-none; only the ~40–44px button isauto. Click amount field and both token triggers around the seam.z-50+ on the stack can cover Settings, TermsGate, risk modal, or portaled pickers.position: fixedon the flip.outline: nonewithout:focus-visiblehides the only keyboard affordance.overflow: hiddencan clip the tap target so the top of the button does not receive clicks.SwapPagecould change hybrid route or wrap combo gas.route/solve, or execute msgs.dangerouslySetInnerHTML.Out of scope as attack surface: contracts, indexer, fee math.
Verification criteria
Done when a reviewer on
make dev(or production-equivalent CSS) can open/in dark and light, at ~375px and ~1280px, and the Pay/Receive hairline stops at the button chrome in idle, hover, and:focus-visible, while flip behavior, Swap IO allowlist, and design-token checks are unchanged.Not done if the line is gone only because the entire seam was deleted; if hover re-exposes it; if the button is transparent glass again; or if chrome-nesting / token scripts fail.
Evidence: before/after crops of the seam (dark + light) in the MR; note viewport. No user identity in screenshots or issue comments.
mentioned in merge request !1155
mentioned in commit
74d39eb78amentioned in commit
98b2da570fmentioned in commit
723c426856Merged to
mainvia !1155 (conflict-free). Pipelines were not green (ci_quota_exceeded); merged without waiting CI per batch instruction.Leftover: visual QA on live Swap at 375/1280 light+dark that the flip plate still occludes the Pay/Receive seam.
make verify-issue-659when the frontend is rebuilt from currentmain.mentioned in issue #673