feat(frontend): hide test tokens behind Advanced settings #193

Open
opened 2026-09-13 21:12:43 +00:00 by PlasticDigits · 0 comments

Summary

Hide known noneconomic faucet tokens from the default Transfer token picker. Keep them available for QA behind Settings → Advanced → Show test tokens (off by default). Ranking from #136 / PR #155 stays when the toggle is on.

This is not a reopen of #136. That issue shipped display order only and listed hiding as out of scope (INV-FE-TOKEN-RANK-1 “Do not hide test tokens”). This issue supersedes that one clause for the Transfer combobox.

Bundle (do not split):

  1. Default hide — Transfer Amount combobox (data-testid="token-select") omits closed-denylist noneconomic ids unless the Advanced toggle is on.
  2. Advanced settings — new Settings tab with an explicit, persistent Show test tokens control (not a hidden key chord).
  3. Empty / selection behavior — hidden ids cannot remain selected; test-only routes get a visible empty state that points at the setting.
  4. Docs + skill — update INV-FE-TOKEN-RANK-1 (or add a sibling INV) and skills/agent-frontend-token-rank.md.

Current codebase

#136 is closed and merged. Test tokens still appear in the Transfer picker; they sort last.

Area Behavior today
Options builder buildTransferTokens() in packages/frontend/src/services/transfer/buildTransferTokens.ts filters by enabled registry + dest mapping, then rankTransferTokens only — no hide
Classification Closed denylist in packages/frontend/src/utils/tokenEconomicRank.ts (isNoneconomicBridgeToken) from packages/frontend/src/utils/faucetTokens.ts
Default selection TransferForm useEffect (~377–392) calls defaultTransferTokenId(transferTokens, selectedTokenId) — first ranked id when empty/invalid; keeps an explicit still-valid test id
Listbox TokenSelect renders given order; tokens.length === 0 returns null (picker vanishes with no copy)
Settings SettingsPage.tsx tabs: Chains, Tokens, Bridge Config, Faucet. No Advanced tab. useUIStore only holds EVM wallet modal
Faucet FaucetPanel.tsx still lists testa / testb / tdec (mainnet) and TKNA/B/C / KDEC (local) via the shared catalog
Settings → Tokens Full Terra registry dump (TokensPanel.tsx) including test CW20s — operator/read-only, not the Transfer picker
Persistence Theme uses localStorage cl8y-theme. No preference store for token visibility
Invariant docs/FRONTEND_BRIDGE_INVARIANTS.md INV-FE-TOKEN-RANK-1 includes Do not hide test tokens
Skill skills/agent-frontend-token-rank.md — “Do not use this skill to hide test tokens”
E2E packages/frontend/e2e/token-selection.spec.ts assumes noneconomic rows are visible in the listbox

Noneconomic ids (same catalog as ranking): mainnet testa / testb / tdec (Terra CW20 + EVM + SPL in faucetTokens.ts); local TKNA / TKNB / TKNC / KDEC. Local uluna / tLUNC and synthetic SOL with noneconomic: false stay economic and must remain in the default picker.


Why this is needed

  1. Mainnet Transfer is a product surface. testa / testb / tdec stay registered so QA can exercise routes. After #136 they no longer win default selection, but they still occupy the dropdown next to LUNC / CL8Y / USTC. Users can still start a noneconomic transfer by picking from the bottom of the list.
  2. QA still needs them. Settings → Faucet claims and an Advanced toggle must keep testa/testb/tdec (and local TKN*) selectable without a rebuild or env flag that production users would hit.
  3. Empty picker is currently silent. Hiding without an empty state would make TokenSelect return null on test-only routes.

Constraints & guardrails

  1. Reuse the #136 denylist. Filter with isNoneconomicBridgeToken / faucetTokens.ts. Do not classify by display symbol, substring "test", or “not in tokenlist.json”. Unknown registered ids stay visible (economic).
  2. Display/filter only. Must not change id / tokenId / evmTokenAddress, dest mappings, decimals, fees, hash encoding, or on-chain enablement. Hidden tokens remain fully bridgeable once shown.
  3. Default off everywhere (mainnet and local). No import.meta.env.DEV auto-show that would leak test tokens on a mis-built production bundle.
  4. When Advanced is on, INV-FE-TOKEN-RANK-1 still applies: economic contiguous at top, noneconomic contiguous at bottom; defaultTransferTokenId unchanged in spirit (first visible token; keep explicit still-valid choice among visible options).
  5. Turning the toggle off must drop a selected hidden token onto the first remaining economic id (or empty). Do not keep a hidden id in React state and submit it.
  6. Test-only route (all mapped tokens noneconomic, toggle off): do not silently re-show test tokens. Show an empty Amount/token area plus copy that Advanced → Show test tokens is required. Faucet claims stay on Settings → Faucet.
  7. Settings → Faucet is not the Transfer picker. Leave faucet claim rows visible (that is the testing claim surface). Do not reorder Settings → Tokens as part of this issue. Optional: a one-line note on the Faucet tab that Transfer hides these until Advanced is on.
  8. New Settings tab “Advanced” (or equivalent labelled section on Settings). Toggle label in plain language, e.g. Show test tokens / “Noneconomic faucet tokens (testa, testb, tdec) in the Transfer picker”. Persist in first-party localStorage (suggested key cl8y-bridge-show-test-tokens, values '0'/'1'). Corrupt/missing → off. This is first-party UI preference, same class as cl8y-theme, not third-party SDK consent. Do not block on #165; if that later gates first-party keys, this key should be listed as functional preference.
  9. No URL ?token= / ?showTestTokens= that silently injects a hidden mint into the default picker. Deep links are out of scope.
  10. No on-chain / operator / registry schema changes. Frontend-only.
  11. Spoof resistance unchanged: test CW20 with symbol: 'CL8Y' still hidden by default; CL8Y CW20 with symbol: 'testa' still shown.
  12. Do not break mapping-load empty [] (legacy #89), INV-UX1 amount/CTA, recipient validation, clickwrap, or faucet claim amounts.
  13. No secrets. Catalog ids are already public.

Relevant files

Path Why
packages/frontend/src/utils/tokenEconomicRank.ts Add filterTransferTokensForDisplay(tokens, showTestTokens) (or equivalent); keep rank helper
packages/frontend/src/utils/tokenEconomicRank.test.ts Hide/show, spoof, empty-after-filter, default-id among visible only
packages/frontend/src/services/transfer/buildTransferTokens.ts Apply filter after mapping filter, before or after rank (rank on the visible set). Thread showTestTokens through or filter at the call site
packages/frontend/src/services/transfer/buildTransferTokens.test.ts Builder paths (Terra/Solana, EVM mappings, fallback) honor the flag
packages/frontend/src/components/transfer/TransferForm.tsx Read preference; pass into builder; default selection on visible list; empty copy
packages/frontend/src/components/transfer/TokenSelect.tsx / AmountInput.tsx Empty state when tokens.length === 0 after hide (do not leave a blank Amount row)
packages/frontend/src/pages/SettingsPage.tsx Fifth tab Advanced; update tab count tests
New packages/frontend/src/components/settings/AdvancedPanel.tsx (+ test) Toggle + short help
New hook/store e.g. packages/frontend/src/hooks/useShowTestTokens.ts localStorage read/write, storage event so Settings and Transfer stay in sync across tabs
packages/frontend/src/pages/SettingsPage.test.tsx Tab present; toggle default off
packages/frontend/e2e/token-selection.spec.ts Default list has no noneconomic ids; with preference on, ranking test still passes; selecting testa requires the flag
docs/FRONTEND_BRIDGE_INVARIANTS.md Replace “Do not hide” with hide-by-default + Advanced override
skills/agent-frontend-token-rank.md Visibility rule + catalog still shared with Faucet
packages/frontend/src/utils/faucetTokens.ts Catalog only; no duplicate denylist

  1. Single visible-set helper next to ranking:

    visible = showTestTokens ? ranked : ranked.filter(t => !isNoneconomicBridgeToken(t))

    Rank the full mapped set first (stable groups), then drop noneconomic when the flag is off — or filter then rank; both must be unit-tested. Prefer filter then rank on the list actually rendered so tokens[0] is always a valid default.

  2. Preference module with:

    • getShowTestTokens(): boolean default false
    • setShowTestTokens(next: boolean)
    • React hook that re-renders Transfer when Settings saves
    • SSR/test: no window → false
  3. Settings → Advanced tab containing only this control for this MR (do not dump RPC URLs, private keys, or operator config here).

  4. Empty state in the Transfer amount/token cluster when visible.length === 0 while mappings have finished: short message + link/button to /settings Advanced. Distinguish from EVM mapping-load (still [] with no “enable test tokens” copy).

  5. E2E: set the storage key in a fixture helper before tests that need TKNA/testa. Default-path tests must not enable it.

  6. Keep TokenSelect from applying a second sort.


Definition of Ready

  • Current Transfer builder + ranking + Settings tabs inspected on code/cl8y-bridge-monorepo default branch
  • Prior ranking issue #136 / PR #155 identified; hiding was explicitly out of scope there
  • Catalog and classifier already exist (faucetTokens.ts, isNoneconomicBridgeToken)
  • Constraints, files, direction, acceptance, tests, and verification written below
  • Given/When/Then implementable without a research spike

Acceptance criteria

  • Fresh visit (no localStorage key): Transfer combobox contains zero noneconomic catalog ids when economic tokens exist for the route; default is economic.
  • testa / testb / tdec (mainnet) and TKNA/B/C / KDEC (local) are absent from the listbox until Show test tokens is on.
  • Local tLUNC / uluna remain in the default picker (noneconomic: false).
  • Settings → Advanced toggle defaults off; turning it on immediately includes noneconomic tokens in Transfer (no remount of the whole app required beyond normal React state). Reload keeps the preference.
  • With toggle on: economic-then-test order, spoof-by-id classification, and “keep explicit test selection while valid” still hold.
  • Toggle off while testa is selected: selection moves to first visible economic token; submit/hash uses that id, not the hidden one.
  • Test-only mapped route + toggle off: picker empty state with Advanced pointer; faucet panel still lists claimable test tokens.
  • Settings → Faucet / Tokens order and claim behavior unchanged (except optional helper sentence on Faucet).
  • No ?token= / query flag added.
  • Unit tests cover hide/show, spoof, corrupt storage, all builder return paths, and default-id among visible tokens.
  • Playwright: default list has no noneconomic data-tokenid; with storage key set, ranking + select-testa specs pass.
  • Invariant + skill updated; make test-frontend / package unit tests green.

Given / When / Then

Given a Transfer route whose mapped set includes at least one economic token and at least one noneconomic faucet token, and Show test tokens is off (default)
When the user opens the Amount token combobox
Then noneconomic ids are not in [role="option"], the default selection is economic, and a later Bridge submit cannot use a hidden id.

Given the same route and the user enables Settings → Advanced → Show test tokens
When they return to Transfer and open the combobox
Then noneconomic tokens appear in the bottom group (INV-FE-TOKEN-RANK-1) and selecting testa keeps testa through amount entry.

Given testa is selected with the toggle on
When the user turns the toggle off
Then the selected id becomes the first remaining visible token (economic if any); the hidden testa id is not submitted.

Given a route whose mapped tokens are all noneconomic and the toggle is off
When mappings have finished loading
Then the UI shows an empty-token message pointing at Advanced settings, not a blank TokenSelect and not an automatic unhide.


Test plan — functional paths

Unit (rank helper + builder)

  • Mixed set, showTestTokens=false: output is only economic; order matches rank of that subset.
  • Mixed set, showTestTokens=true: economic then test (existing #136 cases still pass).
  • Economic only: flag true/false identical.
  • Test only, flag false: [].
  • Test only, flag true: all remain, ranked.
  • Spoof: test CW20 + symbol: 'CL8Y' hidden when flag false; CL8Y CW20 + symbol: 'testa' still visible.
  • Unknown registry id (not denylisted) remains visible when flag false.
  • Case-insensitive EVM / SPL aliases still hide.
  • defaultTransferTokenId on the visible list; empty visible → undefined.
  • Terra/Solana path, EVM sourceChainMappings path, registry evm_token_address fallback, and mapping-load [] all honor the flag (load-empty must not be confused with hide-empty).
  • Corrupt localStorage / missing key → false.
  • noneconomic: false faucet rows (tLUNC) never hidden by this filter.

Settings / preference

  • Settings has Advanced tab; toggle aria reflects off by default.
  • Toggling writes storage; Transfer hook observes it (same-document state and storage event if implemented).
  • Existing four tabs still work; Advanced does not expose operator secrets.

TransferForm / TokenSelect

  • Flag off + mixed tokens: no noneconomic option; default economic.
  • Flag on: can select testa; amount fill does not snap back to LUNC.
  • Flag off after testa selected: selection becomes economic; no submit of hidden id.
  • Visible empty after hide: Amount cluster shows empty copy + Settings pointer; data-testid="token-select" may be absent, but the message is queryable (add data-testid e.g. token-select-empty).
  • EVM mappings still loading: no “enable test tokens” empty copy.

Playwright

  • Default: open listbox; every data-tokenid fails isNoneconomicBridgeToken.
  • Enable storage key in fixture; existing economic-before-test and select-noneconomic tests pass.
  • Audit other e2e/*.spec.ts for first-token / TKNA / testa assumptions; those that need test tokens must set the preference in the same MR.

Manual

  • Production-like mixed route: default picker is LUNC/CL8Y/… only; Advanced on shows testa/testb/tdec at bottom.
  • Faucet still claims testa.
  • Reload twice: preference sticky; default remains hide for a cleared-storage window.
  • Terra ↔ EVM / Solana pairs: same hide rule.

Classification spoof, hidden-id submit, and empty-list clickjack belong in the unit/component cases above. This is not an auth/keys/wasm/deploy/wallet/2-of-3 change — no extra DeFi attack suite.


Verification criteria

  1. Automated: New filter/preference tests pass; existing rank tests still pass when showTestTokens=true. Frontend unit suite green.
  2. E2E: Default token-selection spec asserts no noneconomic ids; flag-on specs cover ranking + testa select; specs that assumed visible TKNA are updated.
  3. Manual: On a build with CL8Y/LUNC and testa routed: default dropdown has no testa; Advanced on restores bottom-group testa and a successful testa selection still encodes testa’s id.
  4. Docs: Invariant + skill no longer say “do not hide”; they describe default hide + Advanced override. MR links this issue.
  5. No regression: Faucet claims, mapping-load gating, INV-UX1, clickwrap, ranking-when-shown.

Out of scope

  • Unregistering or disabling testa/testb/tdec on-chain
  • Hiding Settings → Faucet claim buttons
  • Reordering Settings → Tokens
  • Token search, favorites, balance sort
  • CCTP / operator / contract work
  • #165 storage-consent banner (list the new key if that issue later enumerates first-party preferences)

References

  • Ranking (shipped): #136, PR #155
  • Catalog: packages/frontend/src/utils/faucetTokens.ts
  • Invariant: docs/FRONTEND_BRIDGE_INVARIANTS.md INV-FE-TOKEN-RANK-1
  • Skill: skills/agent-frontend-token-rank.md

First-pass model recommendation

Recommendation: grok-high

Rationale: Not a Composer-shaped first pass. Visibility is a cross-cutting UI preference (new Settings tab + localStorage hook + Transfer builder + empty state + e2e/docs/skill). Expected production files exceed three in one subsystem (tokenEconomicRank / buildTransferTokens, TransferForm + empty TokenSelect/Amount copy, SettingsPage + new Advanced panel, preference hook). It revises a shipped invariant (INV-FE-TOKEN-RANK-1 “do not hide”) and can empty the picker or submit the wrong mint if filter and selectedTokenId disagree. Comparable prior work is PR #155 (ranking: builder, catalog extract, TransferForm, TokenSelect tests, e2e, invariants, skill — far past a 1–3 file local edit). Verification is unit + Playwright + mixed-route manual, not a single deterministic helper test. Composer’s “at most three production files / no cross-cutting state” criteria fail; use Grok high.


Labels

feature (type helper after create). Do not apply ready / agent:implement via intake labels.

## Summary Hide known noneconomic faucet tokens from the **default Transfer token picker**. Keep them available for QA behind **Settings → Advanced → Show test tokens** (off by default). Ranking from [#136](https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/136) / [PR #155](https://git.cl8y.com/code/cl8y-bridge-monorepo/pulls/155) stays when the toggle is on. This is **not** a reopen of #136. That issue shipped display order only and listed hiding as out of scope (`INV-FE-TOKEN-RANK-1` “Do not hide test tokens”). This issue supersedes that one clause for the Transfer combobox. Bundle (do not split): 1. **Default hide** — Transfer Amount combobox (`data-testid="token-select"`) omits closed-denylist noneconomic ids unless the Advanced toggle is on. 2. **Advanced settings** — new Settings tab with an explicit, persistent **Show test tokens** control (not a hidden key chord). 3. **Empty / selection behavior** — hidden ids cannot remain selected; test-only routes get a visible empty state that points at the setting. 4. **Docs + skill** — update `INV-FE-TOKEN-RANK-1` (or add a sibling INV) and `skills/agent-frontend-token-rank.md`. --- ## Current codebase [#136](https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/136) is **closed and merged**. Test tokens still appear in the Transfer picker; they sort last. | Area | Behavior today | |------|----------------| | Options builder | `buildTransferTokens()` in `packages/frontend/src/services/transfer/buildTransferTokens.ts` filters by enabled registry + dest mapping, then **`rankTransferTokens` only** — no hide | | Classification | Closed denylist in `packages/frontend/src/utils/tokenEconomicRank.ts` (`isNoneconomicBridgeToken`) from `packages/frontend/src/utils/faucetTokens.ts` | | Default selection | `TransferForm` `useEffect` (~377–392) calls `defaultTransferTokenId(transferTokens, selectedTokenId)` — first ranked id when empty/invalid; keeps an explicit still-valid test id | | Listbox | `TokenSelect` renders given order; **`tokens.length === 0` returns `null`** (picker vanishes with no copy) | | Settings | `SettingsPage.tsx` tabs: Chains, Tokens, Bridge Config, Faucet. **No Advanced tab.** `useUIStore` only holds EVM wallet modal | | Faucet | `FaucetPanel.tsx` still lists testa / testb / tdec (mainnet) and TKNA/B/C / KDEC (local) via the shared catalog | | Settings → Tokens | Full Terra registry dump (`TokensPanel.tsx`) including test CW20s — operator/read-only, not the Transfer picker | | Persistence | Theme uses `localStorage` `cl8y-theme`. No preference store for token visibility | | Invariant | `docs/FRONTEND_BRIDGE_INVARIANTS.md` **INV-FE-TOKEN-RANK-1** includes **Do not hide test tokens** | | Skill | `skills/agent-frontend-token-rank.md` — “Do not use this skill to hide test tokens” | | E2E | `packages/frontend/e2e/token-selection.spec.ts` assumes noneconomic rows are visible in the listbox | **Noneconomic ids (same catalog as ranking):** mainnet testa / testb / tdec (Terra CW20 + EVM + SPL in `faucetTokens.ts`); local TKNA / TKNB / TKNC / KDEC. Local `uluna` / tLUNC and synthetic SOL with `noneconomic: false` stay **economic** and must remain in the default picker. --- ## Why this is needed 1. **Mainnet Transfer is a product surface.** testa / testb / tdec stay registered so QA can exercise routes. After #136 they no longer win default selection, but they still occupy the dropdown next to LUNC / CL8Y / USTC. Users can still start a noneconomic transfer by picking from the bottom of the list. 2. **QA still needs them.** Settings → Faucet claims and an Advanced toggle must keep testa/testb/tdec (and local TKN*) selectable without a rebuild or env flag that production users would hit. 3. **Empty picker is currently silent.** Hiding without an empty state would make `TokenSelect` return `null` on test-only routes. --- ## Constraints & guardrails 1. **Reuse the #136 denylist.** Filter with `isNoneconomicBridgeToken` / `faucetTokens.ts`. Do **not** classify by display `symbol`, substring `"test"`, or “not in `tokenlist.json`”. Unknown registered ids stay visible (economic). 2. **Display/filter only.** Must not change `id` / `tokenId` / `evmTokenAddress`, dest mappings, decimals, fees, hash encoding, or on-chain enablement. Hidden tokens remain fully bridgeable once shown. 3. **Default off everywhere** (mainnet and local). No `import.meta.env.DEV` auto-show that would leak test tokens on a mis-built production bundle. 4. **When Advanced is on, INV-FE-TOKEN-RANK-1 still applies:** economic contiguous at top, noneconomic contiguous at bottom; `defaultTransferTokenId` unchanged in spirit (first visible token; keep explicit still-valid choice among **visible** options). 5. **Turning the toggle off must drop a selected hidden token** onto the first remaining economic id (or empty). Do not keep a hidden id in React state and submit it. 6. **Test-only route (all mapped tokens noneconomic, toggle off):** do **not** silently re-show test tokens. Show an empty Amount/token area plus copy that Advanced → Show test tokens is required. Faucet claims stay on Settings → Faucet. 7. **Settings → Faucet is not the Transfer picker.** Leave faucet claim rows visible (that is the testing claim surface). Do not reorder Settings → Tokens as part of this issue. Optional: a one-line note on the Faucet tab that Transfer hides these until Advanced is on. 8. **New Settings tab “Advanced”** (or equivalent labelled section on Settings). Toggle label in plain language, e.g. **Show test tokens** / “Noneconomic faucet tokens (testa, testb, tdec) in the Transfer picker”. Persist in first-party `localStorage` (suggested key `cl8y-bridge-show-test-tokens`, values `'0'`/`'1'`). Corrupt/missing → **off**. This is first-party UI preference, same class as `cl8y-theme`, not third-party SDK consent. Do **not** block on [#165](https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/165); if that later gates first-party keys, this key should be listed as functional preference. 9. **No URL `?token=` / `?showTestTokens=`** that silently injects a hidden mint into the default picker. Deep links are out of scope. 10. **No on-chain / operator / registry schema changes.** Frontend-only. 11. **Spoof resistance unchanged:** test CW20 with `symbol: 'CL8Y'` still hidden by default; CL8Y CW20 with `symbol: 'testa'` still shown. 12. **Do not break** mapping-load empty `[]` (legacy #89), INV-UX1 amount/CTA, recipient validation, clickwrap, or faucet claim amounts. 13. **No secrets.** Catalog ids are already public. --- ## Relevant files | Path | Why | |------|-----| | `packages/frontend/src/utils/tokenEconomicRank.ts` | Add `filterTransferTokensForDisplay(tokens, showTestTokens)` (or equivalent); keep rank helper | | `packages/frontend/src/utils/tokenEconomicRank.test.ts` | Hide/show, spoof, empty-after-filter, default-id among visible only | | `packages/frontend/src/services/transfer/buildTransferTokens.ts` | Apply filter **after** mapping filter, **before or after** rank (rank on the visible set). Thread `showTestTokens` through or filter at the call site | | `packages/frontend/src/services/transfer/buildTransferTokens.test.ts` | Builder paths (Terra/Solana, EVM mappings, fallback) honor the flag | | `packages/frontend/src/components/transfer/TransferForm.tsx` | Read preference; pass into builder; default selection on **visible** list; empty copy | | `packages/frontend/src/components/transfer/TokenSelect.tsx` / `AmountInput.tsx` | Empty state when `tokens.length === 0` after hide (do not leave a blank Amount row) | | `packages/frontend/src/pages/SettingsPage.tsx` | Fifth tab **Advanced**; update tab count tests | | New `packages/frontend/src/components/settings/AdvancedPanel.tsx` (+ test) | Toggle + short help | | New hook/store e.g. `packages/frontend/src/hooks/useShowTestTokens.ts` | `localStorage` read/write, `storage` event so Settings and Transfer stay in sync across tabs | | `packages/frontend/src/pages/SettingsPage.test.tsx` | Tab present; toggle default off | | `packages/frontend/e2e/token-selection.spec.ts` | Default list has **no** noneconomic ids; with preference on, ranking test still passes; selecting testa requires the flag | | `docs/FRONTEND_BRIDGE_INVARIANTS.md` | Replace “Do not hide” with hide-by-default + Advanced override | | `skills/agent-frontend-token-rank.md` | Visibility rule + catalog still shared with Faucet | | `packages/frontend/src/utils/faucetTokens.ts` | Catalog only; no duplicate denylist | --- ## Recommended solution direction 1. **Single visible-set helper** next to ranking: `visible = showTestTokens ? ranked : ranked.filter(t => !isNoneconomicBridgeToken(t))` Rank the full mapped set first (stable groups), then drop noneconomic when the flag is off — or filter then rank; both must be unit-tested. Prefer **filter then rank** on the list actually rendered so `tokens[0]` is always a valid default. 2. **Preference module** with: - `getShowTestTokens(): boolean` default `false` - `setShowTestTokens(next: boolean)` - React hook that re-renders Transfer when Settings saves - SSR/test: no `window` → `false` 3. **Settings → Advanced** tab containing only this control for this MR (do not dump RPC URLs, private keys, or operator config here). 4. **Empty state** in the Transfer amount/token cluster when `visible.length === 0` while mappings have finished: short message + link/button to `/settings` Advanced. Distinguish from EVM mapping-load (still `[]` with no “enable test tokens” copy). 5. **E2E:** set the storage key in a fixture helper before tests that need TKNA/testa. Default-path tests must **not** enable it. 6. Keep `TokenSelect` from applying a second sort. --- ## Definition of Ready - [x] Current Transfer builder + ranking + Settings tabs inspected on `code/cl8y-bridge-monorepo` default branch - [x] Prior ranking issue #136 / PR #155 identified; hiding was explicitly out of scope there - [x] Catalog and classifier already exist (`faucetTokens.ts`, `isNoneconomicBridgeToken`) - [x] Constraints, files, direction, acceptance, tests, and verification written below - [x] Given/When/Then implementable without a research spike --- ## Acceptance criteria - [ ] Fresh visit (no `localStorage` key): Transfer combobox contains **zero** noneconomic catalog ids when economic tokens exist for the route; default is economic. - [ ] testa / testb / tdec (mainnet) and TKNA/B/C / KDEC (local) are absent from the listbox until **Show test tokens** is on. - [ ] Local tLUNC / `uluna` remain in the default picker (`noneconomic: false`). - [ ] Settings → Advanced toggle defaults off; turning it on immediately includes noneconomic tokens in Transfer (no remount of the whole app required beyond normal React state). Reload keeps the preference. - [ ] With toggle on: economic-then-test order, spoof-by-id classification, and “keep explicit test selection while valid” still hold. - [ ] Toggle off while testa is selected: selection moves to first visible economic token; submit/hash uses that id, not the hidden one. - [ ] Test-only mapped route + toggle off: picker empty state with Advanced pointer; faucet panel still lists claimable test tokens. - [ ] Settings → Faucet / Tokens order and claim behavior unchanged (except optional helper sentence on Faucet). - [ ] No `?token=` / query flag added. - [ ] Unit tests cover hide/show, spoof, corrupt storage, all builder return paths, and default-id among visible tokens. - [ ] Playwright: default list has no noneconomic `data-tokenid`; with storage key set, ranking + select-testa specs pass. - [ ] Invariant + skill updated; `make test-frontend` / package unit tests green. --- ## Given / When / Then **Given** a Transfer route whose mapped set includes at least one economic token and at least one noneconomic faucet token, and **Show test tokens** is off (default) **When** the user opens the Amount token combobox **Then** noneconomic ids are not in `[role="option"]`, the default selection is economic, and a later Bridge submit cannot use a hidden id. **Given** the same route and the user enables **Settings → Advanced → Show test tokens** **When** they return to Transfer and open the combobox **Then** noneconomic tokens appear in the bottom group (INV-FE-TOKEN-RANK-1) and selecting testa keeps testa through amount entry. **Given** testa is selected with the toggle on **When** the user turns the toggle off **Then** the selected id becomes the first remaining visible token (economic if any); the hidden testa id is not submitted. **Given** a route whose mapped tokens are all noneconomic and the toggle is off **When** mappings have finished loading **Then** the UI shows an empty-token message pointing at Advanced settings, not a blank `TokenSelect` and not an automatic unhide. --- ## Test plan — functional paths ### Unit (rank helper + builder) - [ ] Mixed set, `showTestTokens=false`: output is only economic; order matches rank of that subset. - [ ] Mixed set, `showTestTokens=true`: economic then test (existing #136 cases still pass). - [ ] Economic only: flag true/false identical. - [ ] Test only, flag false: `[]`. - [ ] Test only, flag true: all remain, ranked. - [ ] Spoof: test CW20 + `symbol: 'CL8Y'` hidden when flag false; CL8Y CW20 + `symbol: 'testa'` still visible. - [ ] Unknown registry id (not denylisted) remains visible when flag false. - [ ] Case-insensitive EVM / SPL aliases still hide. - [ ] `defaultTransferTokenId` on the **visible** list; empty visible → `undefined`. - [ ] Terra/Solana path, EVM `sourceChainMappings` path, registry `evm_token_address` fallback, and mapping-load `[]` all honor the flag (load-empty must not be confused with hide-empty). - [ ] Corrupt `localStorage` / missing key → `false`. - [ ] `noneconomic: false` faucet rows (tLUNC) never hidden by this filter. ### Settings / preference - [ ] Settings has Advanced tab; toggle `aria` reflects off by default. - [ ] Toggling writes storage; Transfer hook observes it (same-document state and `storage` event if implemented). - [ ] Existing four tabs still work; Advanced does not expose operator secrets. ### TransferForm / TokenSelect - [ ] Flag off + mixed tokens: no noneconomic option; default economic. - [ ] Flag on: can select testa; amount fill does not snap back to LUNC. - [ ] Flag off after testa selected: selection becomes economic; no submit of hidden id. - [ ] Visible empty after hide: Amount cluster shows empty copy + Settings pointer; `data-testid="token-select"` may be absent, but the message is queryable (add `data-testid` e.g. `token-select-empty`). - [ ] EVM mappings still loading: no “enable test tokens” empty copy. ### Playwright - [ ] Default: open listbox; every `data-tokenid` fails `isNoneconomicBridgeToken`. - [ ] Enable storage key in fixture; existing economic-before-test and select-noneconomic tests pass. - [ ] Audit other `e2e/*.spec.ts` for first-token / TKNA / testa assumptions; those that need test tokens must set the preference in the same MR. ### Manual - [ ] Production-like mixed route: default picker is LUNC/CL8Y/… only; Advanced on shows testa/testb/tdec at bottom. - [ ] Faucet still claims testa. - [ ] Reload twice: preference sticky; default remains hide for a cleared-storage window. - [ ] Terra ↔ EVM / Solana pairs: same hide rule. Classification spoof, hidden-id submit, and empty-list clickjack belong in the unit/component cases above. This is not an auth/keys/wasm/deploy/wallet/2-of-3 change — no extra DeFi attack suite. --- ## Verification criteria 1. **Automated:** New filter/preference tests pass; existing rank tests still pass when `showTestTokens=true`. Frontend unit suite green. 2. **E2E:** Default token-selection spec asserts no noneconomic ids; flag-on specs cover ranking + testa select; specs that assumed visible TKNA are updated. 3. **Manual:** On a build with CL8Y/LUNC and testa routed: default dropdown has no testa; Advanced on restores bottom-group testa and a successful testa selection still encodes testa’s id. 4. **Docs:** Invariant + skill no longer say “do not hide”; they describe default hide + Advanced override. MR links this issue. 5. **No regression:** Faucet claims, mapping-load gating, INV-UX1, clickwrap, ranking-when-shown. --- ## Out of scope - Unregistering or disabling testa/testb/tdec on-chain - Hiding Settings → Faucet claim buttons - Reordering Settings → Tokens - Token search, favorites, balance sort - CCTP / operator / contract work - [#165](https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/165) storage-consent banner (list the new key if that issue later enumerates first-party preferences) ## References - Ranking (shipped): [#136](https://git.cl8y.com/code/cl8y-bridge-monorepo/issues/136), [PR #155](https://git.cl8y.com/code/cl8y-bridge-monorepo/pulls/155) - Catalog: `packages/frontend/src/utils/faucetTokens.ts` - Invariant: `docs/FRONTEND_BRIDGE_INVARIANTS.md` **INV-FE-TOKEN-RANK-1** - Skill: `skills/agent-frontend-token-rank.md` --- ## First-pass model recommendation Recommendation: grok-high Rationale: Not a Composer-shaped first pass. Visibility is a **cross-cutting UI preference** (new Settings tab + `localStorage` hook + Transfer builder + empty state + e2e/docs/skill). Expected production files exceed three in one subsystem (`tokenEconomicRank` / `buildTransferTokens`, `TransferForm` + empty TokenSelect/Amount copy, `SettingsPage` + new Advanced panel, preference hook). It **revises a shipped invariant** (INV-FE-TOKEN-RANK-1 “do not hide”) and can empty the picker or submit the wrong mint if filter and `selectedTokenId` disagree. Comparable prior work is PR #155 (ranking: builder, catalog extract, TransferForm, TokenSelect tests, e2e, invariants, skill — far past a 1–3 file local edit). Verification is unit + Playwright + mixed-route manual, not a single deterministic helper test. Composer’s “at most three production files / no cross-cutting state” criteria fail; use Grok high. --- ## Labels `feature` (type helper after create). Do not apply `ready` / `agent:implement` via intake labels.
Sign in to join this conversation.
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-bridge-monorepo#193
No description provided.