fix(frontend): Charts pair select must stick on first change #1271

Merged
PlasticDigits merged 2 commits from issue/1266 into main 2026-09-16 22:55:17 +00:00

Summary

  • Idle /charts hero auto-pick no longer overwrites the first Select Pair change to a listed non-hero market.
  • Catalog-head fallback waits for getPair and does not replace a valid bech32 or live pair route.
  • RTL clicks #chart-pair-select (no remount-only pair switch). make verify-issue-1266 covers C1266 plus the C680 subset.

Fixes #1266

Test plan

  • make verify-issue-1266 (ChartsPage MenuSelect RTL T1–T5 + idle-hero guards + docs/skill grep + chrome nesting)
  • make verify-issue-680 (C680-1–C680-8 stay green)
  • Manual: make dev → /charts → Select Pair once to a non-hero listed market → URL and chart match without a second pick; deep-link /charts/{B} still loads B; idle /charts still opens UST1/cUSTC
## Summary - Idle `/charts` hero auto-pick no longer overwrites the first Select Pair change to a listed non-hero market. - Catalog-head fallback waits for `getPair` and does not replace a valid bech32 or live pair route. - RTL clicks `#chart-pair-select` (no remount-only pair switch). `make verify-issue-1266` covers C1266 plus the C680 subset. Fixes #1266 ## Test plan - [x] `make verify-issue-1266` (ChartsPage MenuSelect RTL T1–T5 + idle-hero guards + docs/skill grep + chrome nesting) - [x] `make verify-issue-680` (C680-1–C680-8 stay green) - [ ] Manual: `make dev` → `/charts` → Select Pair once to a non-hero listed market → URL and chart match without a second pick; deep-link `/charts/{B}` still loads B; idle `/charts` still opens UST1/cUSTC
fix(frontend): keep first Charts pair select against idle hero
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
487316cc74
Hero auto-pick on bare /charts now runs only when no pair is committed.
The first Select Pair change navigates to that pair; catalog-head
fallback no longer replaces a valid bech32 while getPair is pending.
Author
Owner

drain skip: no occupying job for rebase/fix-pr/CI-wait; remaining continue

drain skip: no occupying job for rebase/fix-pr/CI-wait; remaining continue
Author
Owner

/agent review

/agent review
Author
Owner

cl8y-agent-control: queued bug_review job 473436fe-bff0-49c7-a8a7-b1da4bc105b4 (not executed; no Hetzner VM).

cl8y-agent-control: queued `bug_review` job `473436fe-bff0-49c7-a8a7-b1da4bc105b4` (not executed; no Hetzner VM).
Author
Owner

RECOMMEND: ACCEPT

Independent MR review (gate VM). Frozen SHA: unset. Head: 487316cc (fix(frontend): keep first Charts pair select against idle hero). Woodpecker CI on PR: Success.


Scope

Frontend-only routing fix for #1266. No smart contracts, indexer/Rust, Postgres, wallet auth, or oracle ingest changes. Security/economic attack surface is limited to Charts URL/query handling and client-side pair selection (read-only charting).


Root cause & fix (correctness)

Severity: High (product bug), fixed

The idle #680 hero useEffect treated selectedPairAddr !== hero on still-bare /charts as “force hero,” racing selectPair’s navigate(/charts/{B}). The fix mirrors Trade #357:

  • shouldAutoPickChartsHeroPair — hero replace-nav only when bare route, pairs loaded, no selectedPairAddr, no userCommittedPairRef (chartsPairRoute.ts:130-143, ChartsPage.tsx:233-259).
  • shouldSnapChartsSelectionToCatalogHead — catalog head cannot replace a valid bech32 or pending getPair (chartsPairRoute.ts:151-167, ChartsPage.tsx:209-231).
  • selectPair early-returns non-bech32 (ChartsPage.tsx:140-141) and sets userCommittedPairRef before state/navigate.

Removed anti-pattern if (!hero || selectedPairAddr === hero) return that caused the clobber when selectedPairAddr === B.

Repro (pre-fix): make dev → /charts → Select Pair once to non-hero B → URL/workspace reverted to hero. Post-fix: RTL AC1/T1 (ChartsPage.test.tsx ~1126) clicks #chart-pair-select without remount.


Issue #1266 acceptance criteria

AC Status Evidence
AC1 first select sticks Met RTL AC1/T1; C1266-1 docs
AC2 idle hero Met AC2/T2; shouldAutoPickChartsHeroPair unit tests
AC3 deep link /charts/{B} Met AC3/T3 (C680-5)
AC4 pager after select Met AC4/T5
AC5 ?price= drop Met AC5 test
AC6 C680 + nesting green Met (CI) PR Woodpecker green; verify-issue-1266 runs full ChartsPage.test.tsx (includes C680 block). Note: verify-issue-1266.sh does not chain make verify-issue-680 (extra util files in #680 script only) — acceptable if CI/main runs both targets separately.

Functional test plan gaps (low):

  • T6 (keyboard MenuSelect) — not added; issue marked Vitest T1–T5 only.
  • T8 — covered by existing C680 suite in same file, not re-run in isolation in verify-1266.
  • PR manual checkbox still unchecked — recommend human once on make dev per issue verification criteria.

Security audit (in-repo vectors)

Vector Severity Result
A1 non-bech32 / javascript: in onChange Low Mitigated — isChartsPairRouteParam(addr) in selectPair (ChartsPage.tsx:141); chartsPairHref null → no navigate. No new RTL for malicious onChange string (existing chartsPairRoute tests).
A2 string concat navigate — N/A — still chartsPairHref + replace: true.
A3 hostile /charts/<script> — Unchanged — C680-5 / CS-11 tests remain.
A4–A6 ?price= / open redirect Low Unchanged — parseChartsPriceQuery / looksHostileChartsPrice; AC5 asserts drop on pair change.
A5 P562 gems — Unchanged — filterRetailDiscoveryIndexerPairs; no selector bypass introduced.
A7 navigate loop Low Hero runs once when idle + empty selection; selectedPairAddr set blocks re-entry.
A8 hero vs select race Was High Fixed by idle-only hero + ref.
A9 XSS via indexer labels Low Unchanged — MenuSelect text paths; no dangerouslySetInnerHTML in diff.
A10 paused/frozen pair Info Charts read-only; freeze banner path unchanged.

Out of scope (no diff): SQL injection, privilege escalation, signing keys, wasm, MEV, oracle manipulation, tokenomic attacks, DB leaks, Rust server ACLs.


DeFi / smart contract / database / oracle

Not applicable to this MR. No on-chain execution, liquidity, or price feed logic changed. Charts continues to consume indexer APIs; routing fix does not alter quote math, TWAP, or hub USD marks.


Test coverage & verify

  • Added: chartsPairRoute guard unit tests (chartsPairRoute.test.ts); 7 RTL cases in #1266 block; make verify-issue-1266 + skill/docs (AGENTS_FRONTEND_CHARTS_PAIR_SELECT.md).
  • Local verify on review VM: make verify-issue-1266 — grep/docs/chrome PASS; Vitest step FAIL (review VM missing nvm/Node; not a code signal). PR CI green is the authoritative test run for this gate.
  • E2E: Issue optional Playwright not added — acceptable for frontend routing with strong RTL.

Gap analysis (non-blocking)

Area Notes
Features Fixes retail blocker; does not add “remember last pair” (correct per constraints).
UX First pick now matches user expectation; idle hero unchanged.
DRY / readability Helpers extracted to chartsPairRoute.ts — testable, parallels shouldAutoPickDefaultTradePair (tradePairRoute.ts:84).
Gas / perf N/A frontend; one ref + predicate checks negligible.
userCommittedPairRef Not reset on deep-link sync (ChartsPage.tsx:121-123); acceptable because selectedPairAddr also gates hero; edge case: back to bare /charts with stale state is pre-existing SPA behavior.
verify-issue-1266 Brittle grep negation on localStorage.setItem in ChartsPage.tsx (currently none) — fine until unrelated storage added.

Packages touched

frontend-dapp (ChartsPage + chartsPairRoute), docs/skills/Makefile/AGENTS only — no indexer/, smartcontracts/, or infra.


Recommendation

Merge after controller autoland (do not merge main from this review). Delivers #1266 with aligned Trade #357 pattern, documented C1266 invariants, and CI-green scope. Optional follow-ups: RTL for T6 keyboard; chain make verify-issue-680 inside verify-issue-1266.sh for AC6 parity; complete manual QA checkbox on PR.

RECOMMEND: ACCEPT Independent MR review (gate VM). Frozen SHA: unset. Head: `487316cc` (`fix(frontend): keep first Charts pair select against idle hero`). Woodpecker CI on PR: **Success**. --- ## Scope Frontend-only routing fix for [#1266](https://git.cl8y.com/code/cl8y-dex-terraclassic/issues/1266). **No** smart contracts, indexer/Rust, Postgres, wallet auth, or oracle ingest changes. Security/economic attack surface is limited to Charts URL/query handling and client-side pair selection (read-only charting). --- ## Root cause & fix (correctness) **Severity: High (product bug), fixed** The idle #680 hero `useEffect` treated `selectedPairAddr !== hero` on still-bare `/charts` as “force hero,” racing `selectPair`’s `navigate(/charts/{B})`. The fix mirrors Trade #357: - `shouldAutoPickChartsHeroPair` — hero replace-nav only when bare route, pairs loaded, **no** `selectedPairAddr`, **no** `userCommittedPairRef` (`chartsPairRoute.ts:130-143`, `ChartsPage.tsx:233-259`). - `shouldSnapChartsSelectionToCatalogHead` — catalog head cannot replace a valid bech32 or pending `getPair` (`chartsPairRoute.ts:151-167`, `ChartsPage.tsx:209-231`). - `selectPair` early-returns non-bech32 (`ChartsPage.tsx:140-141`) and sets `userCommittedPairRef` before state/navigate. Removed anti-pattern `if (!hero || selectedPairAddr === hero) return` that **caused** the clobber when `selectedPairAddr === B`. **Repro (pre-fix):** `make dev` → `/charts` → Select Pair once to non-hero **B** → URL/workspace reverted to hero. **Post-fix:** RTL AC1/T1 (`ChartsPage.test.tsx` ~1126) clicks `#chart-pair-select` without remount. --- ## Issue #1266 acceptance criteria | AC | Status | Evidence | |----|--------|----------| | AC1 first select sticks | **Met** | RTL AC1/T1; `C1266-1` docs | | AC2 idle hero | **Met** | AC2/T2; `shouldAutoPickChartsHeroPair` unit tests | | AC3 deep link `/charts/{B}` | **Met** | AC3/T3 (C680-5) | | AC4 pager after select | **Met** | AC4/T5 | | AC5 `?price=` drop | **Met** | AC5 test | | AC6 C680 + nesting green | **Met** (CI) | PR Woodpecker green; `verify-issue-1266` runs full `ChartsPage.test.tsx` (includes C680 block). **Note:** `verify-issue-1266.sh` does **not** chain `make verify-issue-680` (extra util files in #680 script only) — acceptable if CI/main runs both targets separately. | **Functional test plan gaps (low):** - **T6** (keyboard `MenuSelect`) — not added; issue marked Vitest T1–T5 only. - **T8** — covered by existing C680 suite in same file, not re-run in isolation in verify-1266. - PR manual checkbox still **unchecked** — recommend human once on `make dev` per issue verification criteria. --- ## Security audit (in-repo vectors) | Vector | Severity | Result | |--------|----------|--------| | A1 non-bech32 / `javascript:` in `onChange` | Low | **Mitigated** — `isChartsPairRouteParam(addr)` in `selectPair` (`ChartsPage.tsx:141`); `chartsPairHref` null → no navigate. No new RTL for malicious `onChange` string (existing `chartsPairRoute` tests). | | A2 string concat navigate | — | **N/A** — still `chartsPairHref` + `replace: true`. | | A3 hostile `/charts/<script>` | — | **Unchanged** — C680-5 / CS-11 tests remain. | | A4–A6 `?price=` / open redirect | Low | **Unchanged** — `parseChartsPriceQuery` / `looksHostileChartsPrice`; AC5 asserts drop on pair change. | | A5 P562 gems | — | **Unchanged** — `filterRetailDiscoveryIndexerPairs`; no selector bypass introduced. | | A7 navigate loop | Low | Hero runs once when idle + empty selection; `selectedPairAddr` set blocks re-entry. | | A8 hero vs select race | **Was High** | **Fixed** by idle-only hero + ref. | | A9 XSS via indexer labels | Low | **Unchanged** — MenuSelect text paths; no `dangerouslySetInnerHTML` in diff. | | A10 paused/frozen pair | Info | Charts read-only; freeze banner path unchanged. | **Out of scope (no diff):** SQL injection, privilege escalation, signing keys, wasm, MEV, oracle manipulation, tokenomic attacks, DB leaks, Rust server ACLs. --- ## DeFi / smart contract / database / oracle **Not applicable** to this MR. No on-chain execution, liquidity, or price feed logic changed. Charts continues to consume indexer APIs; routing fix does not alter quote math, TWAP, or hub USD marks. --- ## Test coverage & verify - **Added:** `chartsPairRoute` guard unit tests (`chartsPairRoute.test.ts`); 7 RTL cases in `#1266` block; `make verify-issue-1266` + skill/docs (`AGENTS_FRONTEND_CHARTS_PAIR_SELECT.md`). - **Local verify on review VM:** `make verify-issue-1266` — grep/docs/chrome **PASS**; Vitest step **FAIL** (review VM missing nvm/Node; not a code signal). **PR CI green** is the authoritative test run for this gate. - **E2E:** Issue optional Playwright not added — acceptable for frontend routing with strong RTL. --- ## Gap analysis (non-blocking) | Area | Notes | |------|--------| | **Features** | Fixes retail blocker; does not add “remember last pair” (correct per constraints). | | **UX** | First pick now matches user expectation; idle hero unchanged. | | **DRY / readability** | Helpers extracted to `chartsPairRoute.ts` — testable, parallels `shouldAutoPickDefaultTradePair` (`tradePairRoute.ts:84`). | | **Gas / perf** | N/A frontend; one ref + predicate checks negligible. | | **`userCommittedPairRef`** | Not reset on deep-link sync (`ChartsPage.tsx:121-123`); acceptable because `selectedPairAddr` also gates hero; edge case: back to bare `/charts` with stale state is pre-existing SPA behavior. | | **verify-issue-1266** | Brittle `grep` negation on `localStorage.setItem` in `ChartsPage.tsx` (currently none) — fine until unrelated storage added. | --- ## Packages touched `frontend-dapp` (ChartsPage + chartsPairRoute), docs/skills/Makefile/AGENTS only — no `indexer/`, `smartcontracts/`, or infra. --- ## Recommendation **Merge after controller autoland** (do not merge `main` from this review). Delivers #1266 with aligned Trade #357 pattern, documented C1266 invariants, and CI-green scope. Optional follow-ups: RTL for T6 keyboard; chain `make verify-issue-680` inside `verify-issue-1266.sh` for AC6 parity; complete manual QA checkbox on PR.
Author
Owner

/agent review

/agent review
Author
Owner

cl8y-agent-control: queued bug_review job 7cccd6d6-29c3-4eaf-9d71-c5b6e1a7558d (not executed; no Hetzner VM).

cl8y-agent-control: queued `bug_review` job `7cccd6d6-29c3-4eaf-9d71-c5b6e1a7558d` (not executed; no Hetzner VM).
Author
Owner

RECOMMEND: ACCEPT

Reviewed commit: 487316cc (PR head at review time)
Scope: Frontend-only Charts routing (ChartsPage.tsx, chartsPairRoute.ts, tests/docs/QA). No indexer, Postgres, Rust API, wasm, wallet, or Coolify changes.


Issue #1266 closure check

AC Status Evidence
AC1 First Select Pair on bare /charts sticks to B Met ChartsPage.test.tsx AC1/T1 (~1120–1131): MenuSelect click, pathname /charts/{OTHER}, candles/stats/leaderboard for B
AC2 Idle bare /charts → hero (C680-1) Met AC2/T2 (~1133–1138)
AC3 Deep link /charts/{B} unchanged (C680-5) Met AC3/T3 (~1140–1145)
AC4 Pager/sort after select does not snap Met AC4/T5 (~1156–1178)
AC5 ?price= dropped when not leg of B Met AC5 (~1180–1187)
AC6 verify-issue-680, RTL race, chrome nesting Met make verify-issue-1266 + make verify-issue-680 green on review VM; check_chrome_nesting.py in verify script

Issue attack table A1–A10: unchanged surface; A1/A4/A6 still covered by chartsPairRoute.test.ts + selectPair isChartsPairRouteParam guard (ChartsPage.tsx:140–141). A8 addressed by userCommittedPairRef + shouldAutoPickChartsHeroPair (chartsPairRoute.ts:130–144, ChartsPage.tsx:233–268).

PR test plan gap (non-blocking): Manual make dev step remains unchecked in PR body; T6 (keyboard listbox) not duplicated in Vitest (issue marked optional). Recommend human smoke once before merge.


Root cause & fix (correctness)

The race matched #1266: on bare /charts, hero effect treated selectedPairAddr !== hero as “force hero” while navigate(/charts/B) was still in flight.

Fix aligns with Trade #357 pattern:

  • shouldAutoPickChartsHeroPair — hero only when bare, list ready, no selectedPairAddr, no userCommittedPair (chartsPairRoute.ts:137–144).
  • selectPair sets userCommittedPairRef.current = true before state/nav (ChartsPage.tsx:140–151).
  • shouldSnapChartsSelectionToCatalogHead — no catalog-head snap for valid bech32 or pending getPair (chartsPairRoute.ts:151–167, effect ChartsPage.tsx:209–231).

Pure helpers + unit tests are a good DRY/readability win vs inline effect conditions.


Security audit (in-repo attack models)

Area Result
Smart contracts / DeFi execution N/A — Charts is read-only market data; no swap/limit execution on this page.
Oracle / economic manipulation N/A on-chain; displays indexer/TWAP/oracle reads. No new price inputs.
DB / SQL / indexer leaks No server changes. Pair addr still gated by isChartsPairRouteParam before getPairStats / candles (invalid route notice path unchanged).
Open redirect / XSS No new vectors. Navigation only via chartsPairHref + replace: true. Hostile ?price= still stripped (parseChartsPriceQuery / matchChartsPriceParam).
Access control / privileges N/A — public charts. P562 retail filter unchanged (filterRetailDiscoveryIndexerPairs).
Auth / secrets None touched.
Prompt injection None in diff.

Severity: No BLOCK-class findings.


Testing (executed on review VM)

make verify-issue-1266   # 4/4 steps, Vitest 61 tests (ChartsPage + chartsPairRoute)
make verify-issue-680    # 6/6 steps, Vitest 123 + Trade #524 subset

Woodpecker PR check reported Success via fj pr status 1271.

Coverage gaps (low):

  • T6 keyboard MenuSelect path not asserted (behavior should match T1 via shared onChange).
  • T7 rapid double-select covered (~1189–1208); no dedicated act-spam / infinite-navigate assertion (A7) — mitigated by replace + idle hero guard tests.
  • No Playwright Charts pair change (issue: optional).
  • userCommittedPairRef is never cleared on remount-only navigation back to bare /charts within one SPA session; if product ever adds “reset to hero on nav to /charts”, would need ref reset — out of scope, same class as Trade not persisting last pair.

Gap analysis (non-security)

Dimension Notes
Features / UX Fixes retail-blocking “select twice” bug; idle hero and deep links preserved. Users expect first picker change = URL + chart — now true.
UI/UX No chrome changes (C653 green). Loading copy for off-page getPair unchanged.
Packages frontend-dapp only; Makefile/AGENTS/docs/skills updated consistently.
Gas / contracts N/A
DRY / practices Extracted guards mirror tradePairRoute.shouldAutoPickDefaultTradePair; ref + empty selectedPairAddr guard is idiomatic React.
Indexer / Rust Intentionally untouched per issue.

Minor observations (info / low)

  1. Info — Hero effect deps omit userCommittedPairRef (intentional ref read); safe because selectedPairAddr and navigation side effects cover re-runs (ChartsPage.tsx:260–268).
  2. Low — shouldSnapChartsSelectionToCatalogHead returns true only for invalid bech32 selectedPairAddr off-catalog (chartsPairRoute.ts:165–166); valid user bech32 never snaps — matches C1266-6.

Verdict

Targeted fix for #1266 with strong RTL coverage, documented invariants (C1266-1–8), regression hooks, and no expansion of security-sensitive surface. RECOMMEND: ACCEPT after optional manual /charts smoke (PR checklist item).

Independent gate review; does not replace CI or frozen-SHA controller merge.

RECOMMEND: ACCEPT **Reviewed commit:** `487316cc` (PR head at review time) **Scope:** Frontend-only Charts routing (`ChartsPage.tsx`, `chartsPairRoute.ts`, tests/docs/QA). No indexer, Postgres, Rust API, wasm, wallet, or Coolify changes. --- ## Issue #1266 closure check | AC | Status | Evidence | |----|--------|----------| | **AC1** First Select Pair on bare `/charts` sticks to **B** | Met | `ChartsPage.test.tsx` AC1/T1 (`~1120–1131`): MenuSelect click, pathname `/charts/{OTHER}`, candles/stats/leaderboard for **B** | | **AC2** Idle bare `/charts` → hero (**C680-1**) | Met | AC2/T2 (`~1133–1138`) | | **AC3** Deep link `/charts/{B}` unchanged (**C680-5**) | Met | AC3/T3 (`~1140–1145`) | | **AC4** Pager/sort after select does not snap | Met | AC4/T5 (`~1156–1178`) | | **AC5** `?price=` dropped when not leg of **B** | Met | AC5 (`~1180–1187`) | | **AC6** `verify-issue-680`, RTL race, chrome nesting | Met | `make verify-issue-1266` + `make verify-issue-680` green on review VM; `check_chrome_nesting.py` in verify script | Issue attack table **A1–A10**: unchanged surface; **A1/A4/A6** still covered by `chartsPairRoute.test.ts` + `selectPair` `isChartsPairRouteParam` guard (`ChartsPage.tsx:140–141`). **A8** addressed by `userCommittedPairRef` + `shouldAutoPickChartsHeroPair` (`chartsPairRoute.ts:130–144`, `ChartsPage.tsx:233–268`). **PR test plan gap (non-blocking):** Manual `make dev` step remains unchecked in PR body; **T6** (keyboard listbox) not duplicated in Vitest (issue marked optional). Recommend human smoke once before merge. --- ## Root cause & fix (correctness) The race matched #1266: on bare `/charts`, hero effect treated `selectedPairAddr !== hero` as “force hero” while `navigate(/charts/B)` was still in flight. Fix aligns with Trade #357 pattern: - `shouldAutoPickChartsHeroPair` — hero only when bare, list ready, **no** `selectedPairAddr`, **no** `userCommittedPair` (`chartsPairRoute.ts:137–144`). - `selectPair` sets `userCommittedPairRef.current = true` before state/nav (`ChartsPage.tsx:140–151`). - `shouldSnapChartsSelectionToCatalogHead` — no catalog-head snap for valid bech32 or pending `getPair` (`chartsPairRoute.ts:151–167`, effect `ChartsPage.tsx:209–231`). Pure helpers + unit tests are a good DRY/readability win vs inline effect conditions. --- ## Security audit (in-repo attack models) | Area | Result | |------|--------| | **Smart contracts / DeFi execution** | N/A — Charts is read-only market data; no swap/limit execution on this page. | | **Oracle / economic manipulation** | N/A on-chain; displays indexer/TWAP/oracle reads. No new price inputs. | | **DB / SQL / indexer leaks** | No server changes. Pair addr still gated by `isChartsPairRouteParam` before `getPairStats` / candles (invalid route notice path unchanged). | | **Open redirect / XSS** | No new vectors. Navigation only via `chartsPairHref` + `replace: true`. Hostile `?price=` still stripped (`parseChartsPriceQuery` / `matchChartsPriceParam`). | | **Access control / privileges** | N/A — public charts. **P562** retail filter unchanged (`filterRetailDiscoveryIndexerPairs`). | | **Auth / secrets** | None touched. | | **Prompt injection** | None in diff. | **Severity:** No **BLOCK**-class findings. --- ## Testing (executed on review VM) ``` make verify-issue-1266 # 4/4 steps, Vitest 61 tests (ChartsPage + chartsPairRoute) make verify-issue-680 # 6/6 steps, Vitest 123 + Trade #524 subset ``` Woodpecker PR check reported **Success** via `fj pr status 1271`. **Coverage gaps (low):** - **T6** keyboard `MenuSelect` path not asserted (behavior should match T1 via shared `onChange`). - **T7** rapid double-select covered (`~1189–1208`); no dedicated act-spam / infinite-navigate assertion (A7) — mitigated by `replace` + idle hero guard tests. - No Playwright Charts pair change (issue: optional). - `userCommittedPairRef` is never cleared on remount-only navigation back to bare `/charts` within one SPA session; if product ever adds “reset to hero on nav to `/charts`”, would need ref reset — **out of scope**, same class as Trade not persisting last pair. --- ## Gap analysis (non-security) | Dimension | Notes | |-----------|--------| | **Features / UX** | Fixes retail-blocking “select twice” bug; idle hero and deep links preserved. Users expect first picker change = URL + chart — now true. | | **UI/UX** | No chrome changes (**C653** green). Loading copy for off-page `getPair` unchanged. | | **Packages** | `frontend-dapp` only; Makefile/AGENTS/docs/skills updated consistently. | | **Gas / contracts** | N/A | | **DRY / practices** | Extracted guards mirror `tradePairRoute.shouldAutoPickDefaultTradePair`; ref + empty `selectedPairAddr` guard is idiomatic React. | | **Indexer / Rust** | Intentionally untouched per issue. | --- ## Minor observations (info / low) 1. **Info** — Hero effect deps omit `userCommittedPairRef` (intentional ref read); safe because `selectedPairAddr` and navigation side effects cover re-runs (`ChartsPage.tsx:260–268`). 2. **Low** — `shouldSnapChartsSelectionToCatalogHead` returns `true` only for **invalid** bech32 `selectedPairAddr` off-catalog (`chartsPairRoute.ts:165–166`); valid user bech32 never snaps — matches **C1266-6**. --- ## Verdict Targeted fix for #1266 with strong RTL coverage, documented invariants (**C1266-1–8**), regression hooks, and no expansion of security-sensitive surface. **RECOMMEND: ACCEPT** after optional manual `/charts` smoke (PR checklist item). *Independent gate review; does not replace CI or frozen-SHA controller merge.*
Merge branch 'main' into issue/1266
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
025d05dd18
Author
Owner

fix-pr verification (Grok high). PR already merged; no further code.

Head: 025d05dd (issue/1266, ancestor of main via merge 6f19d582). Issue #1266 is closed. Dual independent reviews already said RECOMMEND: ACCEPT. This pass did not open a sibling issue/N-* PR and did not merge main.

Product fix (already on main)

Idle #680 hero treated selectedPairAddr !== hero on still-bare /charts as force-hero, racing the first #chart-pair-select navigate(/charts/{B}). The landed change:

  • shouldAutoPickChartsHeroPair — hero only when bare, list ready, empty selection, no userCommittedPair
  • selectPair sets userCommittedPairRef before state/nav; non-bech32 is a no-op
  • shouldSnapChartsSelectionToCatalogHead never replaces a valid bech32 or pending getPair

Invariants C1266-1–C1266-8 are in docs/frontend.md § Charts Select Pair, docs/indexer-invariants.md, docs/testing.md, AGENTS.md, and skills/AGENTS_FRONTEND_CHARTS_PAIR_SELECT.md (cross-linked from the UST1 hero + pair-scoped skills).

Review items

Completed / confirmed already done:

  • AC1–AC6 (RTL T1–T5, T4, T7, idle hero, deep link, pager, ?price= drop)
  • A1–A8 href/select guards (chartsPairRoute.test.ts + selectPair bech32 gate)
  • Docs/skill C1266 table + “hero must not override selectPair”
  • Chrome nesting (check_chrome_nesting.py)
  • Independent re-verify after merge (this job)

Not done (review-labeled optional / non-blocking; PR already merged):

  • RTL T6 keyboard MenuSelect (onSelectIndex already shares onChange with click)
  • Chain make verify-issue-680 inside verify-issue-1266.sh (AC6 still met: both targets green on this VM)
  • Reset userCommittedPairRef on bare /charts remount (App uses separate /charts and /charts/:pairAddr routes, so the page remounts; reviewers marked out of scope)
  • Playwright Charts pair change (issue: optional)
  • In-browser make dev smoke — LocalTerra/Docker were down on this gate VM; did not run make setup-cloud-localterra for a merged frontend-only PR. RTL click path is the substitute.

Independent verify (this VM, Node 24)

make verify-issue-1266   # 4/4, Vitest 61 passed (re-run: 61 passed)
make verify-issue-680    # 6/6, Vitest 130 + Trade #524 subset
python3 scripts/check_chrome_nesting.py  # OK

No working-tree changes; nothing to commit or push.

Human checklist (optional, PR body still unchecked)

  • make dev → /charts → Select Pair once to a listed non-hero market → URL + candles + stats + leaderboard match without a second pick
  • Deep-link /charts/{B} still loads B (no hero snap)
  • Idle /charts still opens UST1/cUSTC + ?price=UST1 when listed
  • Keyboard listbox choose B behaves like click (T6)
fix-pr verification (Grok high). PR already merged; no further code. Head: `025d05dd` (`issue/1266`, ancestor of `main` via merge `6f19d582`). Issue #1266 is closed. Dual independent reviews already said **RECOMMEND: ACCEPT**. This pass did not open a sibling `issue/N-*` PR and did not merge `main`. ## Product fix (already on `main`) Idle `#680` hero treated `selectedPairAddr !== hero` on still-bare `/charts` as force-hero, racing the first `#chart-pair-select` `navigate(/charts/{B})`. The landed change: - `shouldAutoPickChartsHeroPair` — hero only when bare, list ready, empty selection, no `userCommittedPair` - `selectPair` sets `userCommittedPairRef` before state/nav; non-bech32 is a no-op - `shouldSnapChartsSelectionToCatalogHead` never replaces a valid bech32 or pending `getPair` Invariants **C1266-1–C1266-8** are in `docs/frontend.md` § Charts Select Pair, `docs/indexer-invariants.md`, `docs/testing.md`, `AGENTS.md`, and `skills/AGENTS_FRONTEND_CHARTS_PAIR_SELECT.md` (cross-linked from the UST1 hero + pair-scoped skills). ## Review items Completed / confirmed already done: - [x] AC1–AC6 (RTL T1–T5, T4, T7, idle hero, deep link, pager, `?price=` drop) - [x] A1–A8 href/select guards (`chartsPairRoute.test.ts` + `selectPair` bech32 gate) - [x] Docs/skill C1266 table + “hero must not override `selectPair`” - [x] Chrome nesting (`check_chrome_nesting.py`) - [x] Independent re-verify after merge (this job) Not done (review-labeled optional / non-blocking; PR already merged): - [ ] RTL **T6** keyboard `MenuSelect` (`onSelectIndex` already shares `onChange` with click) - [ ] Chain `make verify-issue-680` inside `verify-issue-1266.sh` (AC6 still met: both targets green on this VM) - [ ] Reset `userCommittedPairRef` on bare `/charts` remount (App uses separate `/charts` and `/charts/:pairAddr` routes, so the page remounts; reviewers marked out of scope) - [ ] Playwright Charts pair change (issue: optional) - [ ] In-browser `make dev` smoke — LocalTerra/Docker were down on this gate VM; did **not** run `make setup-cloud-localterra` for a merged frontend-only PR. RTL click path is the substitute. ## Independent verify (this VM, Node 24) ``` make verify-issue-1266 # 4/4, Vitest 61 passed (re-run: 61 passed) make verify-issue-680 # 6/6, Vitest 130 + Trade #524 subset python3 scripts/check_chrome_nesting.py # OK ``` No working-tree changes; nothing to commit or push. ## Human checklist (optional, PR body still unchecked) - [ ] `make dev` → `/charts` → Select Pair **once** to a listed non-hero market → URL + candles + stats + leaderboard match without a second pick - [ ] Deep-link `/charts/{B}` still loads **B** (no hero snap) - [ ] Idle `/charts` still opens UST1/cUSTC + `?price=UST1` when listed - [ ] Keyboard listbox choose **B** behaves like click (T6)
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic!1271
No description provided.