feat(frontend): keep layout invariants correct while scrolling #1316
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#1316
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
One product surface: layout invariants that must stay true while the user scrolls the CL8Y DEX dApp.
Bundle (do not split):
MenuSelect/TokenSelect/TokenSearchSelect/ pair search listbox stays anchored to its trigger when the document or an inner scrollport moves..app-top-sticky, and the Trade heading still clears the header.trade-order-ticket-scrollmoves; it does not float over visible Pay / Receive / Expiry / Advanced.fitContent()on routine updates.Related closed work stays closed: #181, #336, #482, #500, #527, #632, #705. Those tickets fixed individual glitches. None of them is a regression that the invariants still hold together after scroll. Do not reopen them.
Current codebase
Scroll-linked rules already live in
docs/frontend.md:.app-top-stickyinfrontend-dapp/src/components/common/Layout.tsxuses an opaque--bg-0so scrolled copy cannot show through. Trade H1 clears the header by about 16px atscrollY = 0.usePortalListboxreadsgetBoundingClientRect()during render and bumps a reducer onwindowscroll (capture),resize, andvisualViewportscroll/resize.computePortalListboxStyleis a pure fixed-position function. Viewport insets (tab bar, in-app browser, finger gap) come fromreadPortalListboxViewport(#632).shrink-0footer, sibling oftrade-order-ticket-scroll, notposition: sticky(T527-1, T527-5). Overlap checks must clip to the visible scrollport.priceChartLightweightSeriesSync.tsusesseries.update()for the live bar.timeScale().fitContent()is only for mount, indicator toggle, and interval switch (#336 / #705).What is missing is a single check that these four rules still hold after scroll, plus a fix for whichever of them fails that check. The capture-phase scroll listener on the portal hook is not itself the bug to rewrite.
Why this is needed
Chrome changes keep landing on the same surfaces (header stack, ticket footer, portaled menus, chart timescale) and each time a scroll invariant regresses in isolation. A user who scrolls
/tradeor/with a picker open, or who has zoomed a chart, should keep the same alignment the docs already promise. Folding that into one pass avoids another round of one-off sticky/scroll tickets.This is frontend layout only. No pair, router, factory, indexer, or wallet-signing change.
Constraints
position: sticky,position: fixed, or a document portal.fitContent()on a 30s candle refresh or a sliding newest-N window at the same interval.visualViewportplus DEX / in-app insets, notinnerHeightalone. Coarse/narrow browse still must not focus a text field.open.html { scrollbar-gutter: stable }stays.shell-panel/card-glass.Relevant files
frontend-dapp/src/components/ui/PortalListbox.tsxfrontend-dapp/src/components/ui/portalListboxPosition.tsfrontend-dapp/src/lib/portalListboxViewport.tsfrontend-dapp/src/components/common/Layout.tsxfrontend-dapp/src/index.css(.app-top-sticky, ticket scroll/footer)frontend-dapp/src/components/charts/priceChartLightweightSeriesSync.tsdocs/frontend.md(portal listbox, sticky header, T527, chart time scale)portalListboxPosition.test.ts,e2e/trade-pair-select-cls.spec.ts,e2e/swap-token-select-viewport.spec.ts,e2e/trade-page-responsive.spec.tsTouch the chart sync file only if a scroll-invariant test shows
fitContent()on refresh. Prefer not to widen the diff into quote, route, or wallet modules.Recommended direction
trade-order-ticket-scrollwhile a portaled listbox is open, and asserts the menu’s top/left track the trigger (tolerance a few pixels) and stay inside the visible band above the mobile tab bar.scrollY > 0on a long route, and T527-1 after ticket-body scroll at ~1280×720 Chromium.docs/frontend.md.Acceptance criteria
Given
/tradeor/at about 1280×720 and about 375×667, with a portaled token or pair listbox openWhen the user scrolls the document or the nearest inner scrollport that contains the trigger
Then the listbox’s fixed top/left follow the trigger’s updated
getBoundingClientRect(), the menu stays inside the visible viewport band, and it does not cover the sticky header or the mobile tab bar.Given
/tradeLimit ticket at scroll-top on Chromium, about 1280×720When the user scrolls
trade-order-ticket-scrollThen the money CTA bottom stays within 8px of
trade-order-ticket-cardbottom, and it does not overlap controls that are actually visible inside the scrollport.Given a price chart whose time scale the user has zoomed or scrolled away from
fitContent()When the 30s candle refresh runs at the same interval
Then the visible logical range is unchanged, and historical bars are not rewritten solely because of that refresh.
Given a long route with
scrollY > 0When page copy passes under the sticky header
Then that copy is not readable through
.app-top-sticky, and the header controls remain reachable.Test plan
portalListboxPosition.test.tsonly if the pure geometry needs a new case (anchor moved, insets unchanged). Do not assert DOM listeners in that file.PLAYWRIGHT_SKIP_CHAIN=1, 5 workers), alongside the existing trade/swap scroll specs: open a listbox, scroll the ancestor, compare trigger vs menu boxes; ticket scroll vs docked CTA; one chart range snapshot across a refetch. No chain, no broadcast.docs/frontend.mdgains an explicit “after scroll” sentence on the Fixed-menu row and points at the new spec. Do not restate closed issues as open work.Verification criteria
maketarget or the existing Vitest files for portal position and chart series sync stay green.First-pass model recommendation
Recommendation: grok-high
Rationale: The failing criterion is scope. Scroll behavior crosses the portal listbox, sticky shell, trade-ticket scrollport, and chart time scale — more than one subsystem, and more than three production files if more than the portal hook is wrong. There is no single known local edit yet; the first pass has to measure which documented invariant breaks and leave the others alone. Chart
fitContent()is easy to regress while fixing layout. Verify with the new Playwright scroll spec plus the existing portal and series-sync unit tests.cl8y-agent-control: queued
design_authorjob32e4f7a0-7156-4047-960d-8537ec6c8731(not executed; no Hetzner VM).cl8y-agent-control: needs_human inbox card POST failed. Job stays parked.