Frontend: Swap page outage banner and simQuery error handling (M1) #241

Closed
opened 2026-05-31 04:41:21 +00:00 by PlasticDigits · 5 comments
PlasticDigits commented 2026-05-31 04:41:21 +00:00 (Migrated from gitlab.com)

Reference

Gap analysis: gaps/GAP_1780200149.md — finding M1.

Current codebase

Market-data routes (Pool, Charts, Limits, Portfolio, Protocol, Trader, Trade) render MarketDataServiceOutageBanner when indexer/LCD queries fail with isIndexerUnavailableError, and show humanized retry copy via humanizeUserFacingError / indexerTradeOutageCopy.

Swap page gaps (frontend-dapp/src/pages/SwapPage.tsx):

  • No MarketDataServiceOutageBanner import or usage (~1277 LOC).
  • simQuery has no isError branch — stale/empty quotes shown silently when simulation fails.
  • Handles swapMutation.isError for tx failures but not quote/sim failures.
  • Uses indexer postRouteSolve / pair sim paths that depend on indexer + LCD availability.

Contrast: PoolPage.tsx:1051-1060, TradePage.tsx:351, ChartsPage.tsx:180.

Why this is needed

Swap is a primary retail entry point. Silent sim failures mislead users into thinking quotes are valid, risking failed txs or wrong slippage expectations. Inconsistent outage UX across routes erodes trust during indexer/LCD incidents.

Constraints / guardrails

  • Reuse existing MarketDataServiceOutageBanner and isIndexerUnavailableError utilities — no new outage components.
  • Distinguish indexer-down vs pair-not-found vs validation errors (mirror Trade page patterns).
  • Do not block wrap/unwrap-only paths that don't need indexer.
  • Disable swap CTA when sim unavailable (match Trade market panel behavior).
  • Follow skills/AGENTS_FRONTEND_QUERY_RETRY.md retry semantics if applicable.

Relevant files

Path Role
frontend-dapp/src/pages/SwapPage.tsx Primary fix
frontend-dapp/src/pages/TradePage.tsx Reference outage pattern
frontend-dapp/src/pages/PoolPage.tsx Reference banner usage
frontend-dapp/src/components/common/MarketDataServiceOutageBanner.tsx Banner component
frontend-dapp/src/utils/humanizeOffChainError.ts Error humanization
frontend-dapp/src/utils/indexerTradeOutageCopy.ts Copy
frontend-dapp/e2e/charts-indexer-outage.spec.ts Outage E2E reference
frontend-dapp/e2e/swap.spec.ts Extend for outage
  1. Compute indexerOutage from simQuery, route-solve queries, and pair list queries (same predicates as other pages).
  2. Render MarketDataServiceOutageBanner at top of swap card when outage detected.
  3. Add simQuery.isError UI: humanized message + retry; hide quote details and disable swap button.
  4. Add Vitest + Playwright outage spec for /swap.

Acceptance criteria

  • Indexer unavailable: banner visible on Swap page.
  • Sim error: humanized error + no stale quote presented as valid.
  • Swap button disabled when quote unavailable.
  • Wrap/unwrap still works when indexer down (if independent).
  • Parity with Trade/Pool outage copy.

Test plan — all paths

Path Test
Indexer up, valid swap No banner; quote shown
Indexer 503 Banner; swap disabled
Sim timeout then retry Error → recovery
Direct pair, indexer down Banner
Wrap path, indexer down Works or shows appropriate msg
Partial route-solve failure Error surfaced

Run: make test-frontend; Playwright outage project if available.

Test plan — attack / abuse vectors

Vector Expected
Malformed indexer 502 HTML body Humanized error; no XSS (no innerHTML)
Flapping indexer Banner toggles; no crash

Verification criteria

  • Vitest: SwapPage renders banner on mocked sim error.
  • Playwright: indexer mock down on /swap shows banner.
  • Manual: stop indexer locally; confirm Swap matches Pool outage UX.
## Reference Gap analysis: [`gaps/GAP_1780200149.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/gaps/GAP_1780200149.md) — finding **M1**. ## Current codebase Market-data routes (Pool, Charts, Limits, Portfolio, Protocol, Trader, Trade) render `MarketDataServiceOutageBanner` when indexer/LCD queries fail with `isIndexerUnavailableError`, and show humanized retry copy via `humanizeUserFacingError` / `indexerTradeOutageCopy`. **Swap page gaps** (`frontend-dapp/src/pages/SwapPage.tsx`): - No `MarketDataServiceOutageBanner` import or usage (~1277 LOC). - `simQuery` has no `isError` branch — stale/empty quotes shown silently when simulation fails. - Handles `swapMutation.isError` for tx failures but not quote/sim failures. - Uses indexer `postRouteSolve` / pair sim paths that depend on indexer + LCD availability. Contrast: `PoolPage.tsx:1051-1060`, `TradePage.tsx:351`, `ChartsPage.tsx:180`. ## Why this is needed Swap is a primary retail entry point. Silent sim failures mislead users into thinking quotes are valid, risking failed txs or wrong slippage expectations. Inconsistent outage UX across routes erodes trust during indexer/LCD incidents. ## Constraints / guardrails - Reuse existing `MarketDataServiceOutageBanner` and `isIndexerUnavailableError` utilities — no new outage components. - Distinguish indexer-down vs pair-not-found vs validation errors (mirror Trade page patterns). - Do not block wrap/unwrap-only paths that don't need indexer. - Disable swap CTA when sim unavailable (match Trade market panel behavior). - Follow `skills/AGENTS_FRONTEND_QUERY_RETRY.md` retry semantics if applicable. ## Relevant files | Path | Role | |------|------| | `frontend-dapp/src/pages/SwapPage.tsx` | Primary fix | | `frontend-dapp/src/pages/TradePage.tsx` | Reference outage pattern | | `frontend-dapp/src/pages/PoolPage.tsx` | Reference banner usage | | `frontend-dapp/src/components/common/MarketDataServiceOutageBanner.tsx` | Banner component | | `frontend-dapp/src/utils/humanizeOffChainError.ts` | Error humanization | | `frontend-dapp/src/utils/indexerTradeOutageCopy.ts` | Copy | | `frontend-dapp/e2e/charts-indexer-outage.spec.ts` | Outage E2E reference | | `frontend-dapp/e2e/swap.spec.ts` | Extend for outage | ## Recommended direction 1. Compute `indexerOutage` from `simQuery`, route-solve queries, and pair list queries (same predicates as other pages). 2. Render `MarketDataServiceOutageBanner` at top of swap card when outage detected. 3. Add `simQuery.isError` UI: humanized message + retry; hide quote details and disable swap button. 4. Add Vitest + Playwright outage spec for `/swap`. ## Acceptance criteria - [ ] Indexer unavailable: banner visible on Swap page. - [ ] Sim error: humanized error + no stale quote presented as valid. - [ ] Swap button disabled when quote unavailable. - [ ] Wrap/unwrap still works when indexer down (if independent). - [ ] Parity with Trade/Pool outage copy. ## Test plan — all paths | Path | Test | |------|------| | Indexer up, valid swap | No banner; quote shown | | Indexer 503 | Banner; swap disabled | | Sim timeout then retry | Error → recovery | | Direct pair, indexer down | Banner | | Wrap path, indexer down | Works or shows appropriate msg | | Partial route-solve failure | Error surfaced | Run: `make test-frontend`; Playwright outage project if available. ## Test plan — attack / abuse vectors | Vector | Expected | |--------|----------| | Malformed indexer 502 HTML body | Humanized error; no XSS (no innerHTML) | | Flapping indexer | Banner toggles; no crash | ## Verification criteria - [ ] Vitest: SwapPage renders banner on mocked sim error. - [ ] Playwright: indexer mock down on `/swap` shows banner. - [ ] Manual: stop indexer locally; confirm Swap matches Pool outage UX.
PlasticDigits commented 2026-05-31 05:14:38 +00:00 (Migrated from gitlab.com)

mentioned in commit bd2cd7047e

mentioned in commit bd2cd7047e48ffc6b7b111906be72f3f04a37167
PlasticDigits commented 2026-05-31 05:15:10 +00:00 (Migrated from gitlab.com)

Implementation complete (GitLab #241)

Merged to main in commit bd2cd70.

Summary

The Swap page (/ and /swap) now matches Trade/Pool/Limits outage UX:

  • MarketDataServiceOutageBanner (swap-market-data-outage-banner) when indexer transport fails during quote (detectSwapIndexerOutage on simQuery + indexerTransportFailed pool-fallback flag).
  • Sim error handling: stale cached quotes are hidden when simQuery.isError; humanized copy + retry; CTA shows Quote unavailable and stays disabled until a fresh sim succeeds.
  • Wrap/unwrap paths unchanged — no indexer calls; swap CTA remains available when other gates pass.
  • Pool fallback parity: when indexer fails but LCD pool sim succeeds, banner still shows but CTA can stay enabled (same as Trade market panel).

Verification checklist

  • Indexer up, valid CW20 swap: no banner; quote + Swap CTA work
  • Stop indexer, enter swap amount: swap-market-data-outage-banner visible; no VITE_INDEXER_URL in DOM
  • Indexer down + pool fallback: banner visible; pool quote shown; CTA enabled when wallet connected
  • Indexer down + sim fully fails: no stale receive amount; Quote unavailable CTA; humanized error copy
  • Wrap/unwrap with indexer down: no outage banner; wrap still submittable
  • Vitest: npm run test:run -- src/pages/SwapPage.test.tsx src/utils/swapIndexerOutage.test.ts
  • Playwright (optional): make test-e2e-indexer-outage includes swap-indexer-outage.spec.ts

@brouie — please verify on local/staging when you have a moment. Leaving issue open until QA sign-off.

## Implementation complete (GitLab #241) Merged to `main` in commit `bd2cd70`. ### Summary The Swap page (`/` and `/swap`) now matches Trade/Pool/Limits outage UX: - **`MarketDataServiceOutageBanner`** (`swap-market-data-outage-banner`) when indexer transport fails during quote (`detectSwapIndexerOutage` on `simQuery` + `indexerTransportFailed` pool-fallback flag). - **Sim error handling**: stale cached quotes are hidden when `simQuery.isError`; humanized copy + retry; CTA shows **Quote unavailable** and stays disabled until a fresh sim succeeds. - **Wrap/unwrap** paths unchanged — no indexer calls; swap CTA remains available when other gates pass. - **Pool fallback parity**: when indexer fails but LCD pool sim succeeds, banner still shows but CTA can stay enabled (same as Trade market panel). ### Docs / agent cross-links - [docs/frontend.md § Swap page — market data outage](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/frontend.md#swap-page-market-data-outage) - [`skills/AGENTS_FRONTEND_MARKET_DATA_OUTAGE.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_FRONTEND_MARKET_DATA_OUTAGE.md) - [`skills/AGENTS_E2E_INDEXER_OUTAGE.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_E2E_INDEXER_OUTAGE.md) ### Verification checklist - [ ] Indexer up, valid CW20 swap: no banner; quote + **Swap** CTA work - [ ] Stop indexer, enter swap amount: `swap-market-data-outage-banner` visible; no `VITE_INDEXER_URL` in DOM - [ ] Indexer down + pool fallback: banner visible; pool quote shown; CTA enabled when wallet connected - [ ] Indexer down + sim fully fails: no stale receive amount; **Quote unavailable** CTA; humanized error copy - [ ] Wrap/unwrap with indexer down: no outage banner; wrap still submittable - [ ] Vitest: `npm run test:run -- src/pages/SwapPage.test.tsx src/utils/swapIndexerOutage.test.ts` - [ ] Playwright (optional): `make test-e2e-indexer-outage` includes `swap-indexer-outage.spec.ts` @brouie — please verify on local/staging when you have a moment. Leaving issue open until QA sign-off.
PlasticDigits commented 2026-05-31 07:15:55 +00:00 (Migrated from gitlab.com)

QA verification complete (agent, worktree verify/issue-241)

Verified commit bd2cd70 on main (841c321 HEAD at time of test). No code changes required — all acceptance and verification criteria pass.

Work performed

  • Git worktree: ../cl8y-dex-terraclassic-verify-241 @ verify/issue-241, synced with origin/main
  • Vitest (10/10 pass):
    npm run test:run -- src/pages/SwapPage.test.tsx src/utils/swapIndexerOutage.test.ts
    • Outage banner on mocked sim error (502)
    • Pool-fallback parity (banner + enabled CTA when LCD sim succeeds)
    • Quote unavailable CTA when sim fully fails
  • Playwright (scripts/test-e2e-indexer-outage.sh): 4/4 pass including e2e/swap-indexer-outage.spec.ts
  • Manual (frontend :5173, indexer stopped without infra reset):
    • Indexer up, CW20 swap: no banner; hybrid quote rendered
    • Indexer down, EMBER→CORAL: banner visible; no env URLs; LCD pool fallback quote (Trade parity)
    • Indexer down, LUNC→LUNC-C wrap: no outage banner

Checklist (all pass)

  • Indexer up, valid CW20 swap: no banner; quote shown
  • Indexer down + swap: banner visible; no VITE_INDEXER_URL in DOM
  • Indexer down + pool fallback: banner + pool quote
  • Sim fully fails: Vitest — Quote unavailable; no stale quote
  • Wrap with indexer down: no banner
  • Vitest + Playwright outage specs
  • Docs: frontend.md, AGENTS_FRONTEND_MARKET_DATA_OUTAGE.md, AGENTS_E2E_INDEXER_OUTAGE.md

Closing — implementation matches Trade/Pool outage UX per gap M1.

## QA verification complete (agent, worktree `verify/issue-241`) Verified commit `bd2cd70` on `main` (`841c321` HEAD at time of test). No code changes required — all acceptance and verification criteria pass. ### Work performed - Git worktree: `../cl8y-dex-terraclassic-verify-241` @ `verify/issue-241`, synced with `origin/main` - **Vitest** (10/10 pass): `npm run test:run -- src/pages/SwapPage.test.tsx src/utils/swapIndexerOutage.test.ts` - Outage banner on mocked sim error (502) - Pool-fallback parity (banner + enabled CTA when LCD sim succeeds) - Quote unavailable CTA when sim fully fails - **Playwright** (`scripts/test-e2e-indexer-outage.sh`): 4/4 pass including `e2e/swap-indexer-outage.spec.ts` - **Manual** (frontend :5173, indexer stopped without infra reset): - Indexer up, CW20 swap: no banner; hybrid quote rendered - Indexer down, EMBER→CORAL: banner visible; no env URLs; LCD pool fallback quote (Trade parity) - Indexer down, LUNC→LUNC-C wrap: no outage banner ### Checklist (all pass) - [x] Indexer up, valid CW20 swap: no banner; quote shown - [x] Indexer down + swap: banner visible; no VITE_INDEXER_URL in DOM - [x] Indexer down + pool fallback: banner + pool quote - [x] Sim fully fails: Vitest — Quote unavailable; no stale quote - [x] Wrap with indexer down: no banner - [x] Vitest + Playwright outage specs - [x] Docs: frontend.md, AGENTS_FRONTEND_MARKET_DATA_OUTAGE.md, AGENTS_E2E_INDEXER_OUTAGE.md Closing — implementation matches Trade/Pool outage UX per gap M1.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-05-31 07:16:01 +00:00
PlasticDigits commented 2026-06-05 12:28:45 +00:00 (Migrated from gitlab.com)

mentioned in merge request !800

mentioned in merge request !800
PlasticDigits commented 2026-06-05 12:28:45 +00:00 (Migrated from gitlab.com)

mentioned in issue #326

mentioned in issue #326
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-dex-terraclassic#241
No description provided.