fix(frontend): Charts pair select must stick on first change #1266
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#1266
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 Charts, choosing a pair from Select Pair (
#chart-pair-select) does not keep that pair on the first change. The workspace (candles, 24h stats, leaderboard, invert pill, URL) stays on the hero / previous pair. A second selection of the same target then sticks.This is the Charts analogue of closed Trade races #357 / #350 / #301 (default-pick / selector clobber). Those tickets are closed and Trade-scoped. Do not reopen them. Open #1258 is USDT USD ingest, not selector routing.
Parent hero work: #680. Related: #666 pair-scoped stats, #547 deep links, #534 catalog order. C680-5 already forbids snapping a valid
/charts/:pairAddrback to the hero; the gap is a userselectPairwhile the route is still bare/charts.Repro
Given Charts is open at bare
/charts(nav item, no pair segment) and the indexer pair list has loaded with UST1/cUSTC as hero plus at least one other listed pair B in#chart-pair-select.When the user opens Select Pair and chooses B once.
Then on that first change the URL is
/charts/{B}(optional?price=for a leg of B only),#chart-pair-selectshows B, and candles / pair stats / leaderboard query B — not the hero. A second identical select is not required.Expected: first
MenuSelectchange is authoritative (same as Trade after #357: auto-pick must not clobber an explicit choice).Actual: first change is overwritten: URL and workspace snap back to the #680 hero (or stay on the previous pair). The second select of B then works, because the route is no longer bare and the hero effect no longer runs.
Current codebase
ChartsPage.tsxkeeps two sources of truth:selectedPairAddranduseParams().pairAddr.selectPair(addr)setsselectedPairAddrandnavigate(chartsPairHref(addr, …), { replace: true }).#chart-pair-selectonChangecallsselectPair(v). Displayed value isactivePairAddr = selectedPairAddr || pairOptions[0]./charts(PRIMARY_NAV_ITEMSpath/charts).isBareChartsisroutePair === undefined.Hero effect (post-#680):
There is no “user already picked a non-hero pair” guard. Trade’s auto-pick returns early when
pairAddris already set (TradePage.tsxshouldAutoPickDefaultTradePair). Charts treats anyselectedPairAddr !== herowhile still bare as a reason to force the hero.Race:
/charts.selectPairqueues state B andnavigate(/charts/B).isBareCharts === truebefore the pair segment commits.B !== hero→ writes hero +navigateto the hero href.isBareCharts === falseand sticks.Secondary (same symptom, later): when
isBareChartsis false but B is not in the current paginatedpairOptionspage, the fallback effect cansetSelectedPairAddr(pairOptions[0])beforegetPair(B)returns (needsPairFetch). Do not leave that snap-back either.Existing tests do not click
#chart-pair-selecton bare/charts:pair switch updates stats and leaderboard args togetherunmounts/chartsand remounts/charts/{B}.MenuSelectforwardsonChangeon option click; this is not a “click twice to open” listbox bug.Why the new implementation is needed
/charts/:pairAddrand not the in-page selector that is supposed to create that URL.Frontend-only. No indexer, no contracts, no Coolify env.
Constraints / guardrails
/charts(no user select) still replace-navigates to UST1/cUSTC +?price=UST1when listed./charts/{other}must not snap to hero. Hostile / non-bech32pairAddrstill shows the invalid notice; no stats/leaderboard fetch with the hostile string (CS-11).cl8y-dex-charts-pair-invert:.chartsPairHref/isChartsPairRouteParam(isValidTerraAddress). No string concat intoNavigate.replace, notpush.?price=: carry only when it is a leg of the newly selected pair (matchChartsPriceParam); otherwise drop. Hostilepriceignored (existing parser).shell-panel*/card-glasschrome.localStorage. Do not change TradePairSearchSelect.python3 scripts/check_chrome_nesting.py.Relevant files
frontend-dapp/src/pages/ChartsPage.tsxselectPair; optional fallback snapfrontend-dapp/src/pages/ChartsPage.test.tsxfrontend-dapp/src/utils/chartsPairRoute.tsfrontend-dapp/src/utils/pairCatalogRank.tsresolveChartsHeroPairAddress— keep behavior, fix call siteskills/AGENTS_FRONTEND_CHARTS_UST1_HERO.md+docs/frontend.md§ Charts UST1/USD heroscripts/qa/verify-issue-680.shverify-issue-<id>for this ticketRecommended direction
selectedPairAddrempty andisBareCharts, or a ref/userSelectedRefset inselectPairthat the hero effect must not override. Do not treatselectedPairAddr !== heroon bare/chartsas “force hero”.navigate(/charts/{addr})is intended, do not write a different addr from effects until the nextselectPair. Trade’sif (pairAddr) returnon auto-pick is the pattern.pairOptions[0]must not run whileneedsPairFetch && selectedPairQuery.isLoading(already skipped) and must not run whenselectedPairAddris a valid bech32 the user just chose (wait forgetPair/ extra row). Never replace a validvalidRoutePair./chartswith hero + B ingetPairs,userEventthe listbox, assert onegetCandles/getPairStats/getLeaderboardfor B andwindow.location/charts/{B}.Acceptance criteria
/charts, list loaded, first Select Pair change to listed B ≠ hero: URL, trigger label, candles, 24h stats, leaderboard all use B. No second select./charts(no click) still auto-picks hero (C680-1)./charts/{B}(listed, non-hero) keeps B (C680-5). Hero effect does not run.pairOptions[0].?price=is dropped when it is not a leg of B; hostile price still ignored.make verify-issue-680(and C680-1–C680-8) stay green. New RTL covers the MenuSelect race.check_chrome_nesting.pygreen.Test plan (functional paths)
/charts, click B in#chart-pair-select/charts/{B}; candles(B); select value B/charts, no click?price=UST1when UST1/cUSTC listed/charts/{B}reloadgetPair); no snap to page[0]MenuSelectkeyboardonSelectIndex)Vitest: RTL T1–T5 on
ChartsPage.test.tsx. Playwright optional (PLAYWRIGHT_SKIP_CHAIN=1): one Charts pair change from nav/charts.Test plan (attack, hack, and abuse)
onChangewith non-bech32 /javascript:/data:///chartsPairHrefreturns null; no navigate; no fetch with that stringpairAddr+ querychartsPairHref+URLSearchParamsvia existing helpers/charts/<script>after a failed selectgetPairStats('<script>')(C680-5)?price=<script>carried across select?price=https://phish/charts/{bech32}replaceonly; RTL no act-spam / infinite navigateselectPairdangerouslySetInnerHTMLVerification criteria
make verify-issue-<id>: ChartsPage RTL T1 + C680 subset + docs/skill grep that hero must not overrideselectPair.make verify-issue-680,make verify-issue-524,python3 scripts/check_chrome_nesting.pystay green.make dev→/charts→ Select Pair once to a non-hero listed market → URL and chart match that pair without a second pick. Deep-link/charts/{B}still loads B. Idle/chartsstill opens UST1/cUSTC.Out of scope
PairSearchSelect(closed #350/#357).First-pass model recommendation
Recommendation: composer
Rationale: One production page (
ChartsPage.tsx) plus focusedChartsPage.test.tsx(optional one-line skill/docs). Same local effect-guard pattern as Trade #357 (if (pairAddr) returnon auto-pick). Low/medium UI-routing risk; no contracts, auth, keys, wasm, wallet/2-of-3, or migrations. Acceptance is deterministic RTL (first#chart-pair-selectchange vs C680-1 idle hero). C680 invariants are already tested; this ticket adds the missing click path rather than redesigning Charts protocol./agent implement
cl8y-agent-control: queued
implementjobd4c0be9c-b8b1-444a-8bad-a35858db354e(not executed; no Hetzner VM).