Frontend: systematic a11y audit — trade/chart/wallet aria gaps + axe in CI #214

Closed
opened 2026-05-29 03:16:34 +00:00 by PlasticDigits · 20 comments
PlasticDigits commented 2026-05-29 03:16:34 +00:00 (Migrated from gitlab.com)

Summary

Close the partial accessibility gap on retail-critical surfaces (trade, price chart, wallet): finish missing aria-* / programmatic names / live regions where canvas and custom widgets hide meaning from assistive tech, and add a repeatable audit gate (@axe-core/playwright and/or eslint-plugin-jsx-a11y) so regressions fail CI instead of relying on ad-hoc source review.

Tracked in gap inventory: gaps/GAP_1780023683.md — Accessibility (Partial: some aria-* on trade/chart/wallet; no systematic a11y audit or axe in CI). Product backlog: same doc § P3 — a11y CI — axe-playwright on critical routes.

Builds on closed label sweep (#143), focus rings (#144), wallet chip (#186), portal listbox (#181), and chart empty-state docs (#151).


Current codebase

What already exists

Area Coverage Representative files
Shell / nav Strong Layout.tsx — aria-label on nav/menus, aria-expanded / role="menu"
Trade ticket Partial–good TradeOrderTicket.tsx — role="tablist" / aria-selected for limit vs market; LimitOrderBidAskSideSelector.tsx — radiogroup + aria-checked; LimitOrderPreSubmitSummary.tsx — role="region"
Order book Partial OrderBookPanel.tsx — per-row aria-label on Edit/Cancel; book is a <ul> grid, not a semantic <table>; column headers are visual <span>s only
Chart Partial PriceChart.tsx — interval role="group", fullscreen aria-pressed / aria-label, interval-loading aria-live; PriceChartOverlayMenu.tsx — aria-expanded / aria-controls; PriceChartLightweightCanvas.tsx — canvas aria-hidden (correct — plot is decorative to AT); PriceChartEmptyState.tsx — role="img" + aria-label
Wallet Partial–good WalletButton.tsx — connected trigger aria-label includes network; menu role="menu" / menuitem; dismiss backdrop labeled; WalletModal.tsx — per-option aria-label with extension detection; WalletLuncBalance.tsx — aria-busy + sr-only loading
Docs / agents Partial docs/frontend.md § form labels, focus visible, chart empty a11y; skills/AGENTS_FRONTEND_A11Y_FORM_LABELS.md, skills/AGENTS_FRONTEND_A11Y_FOCUS.md
Automated a11y None No axe-core, @axe-core/playwright, jest-axe, or eslint-plugin-jsx-a11y in frontend-dapp/package.json; Playwright E2E (playwright.config.ts, 5 workers) has navigation/wallet specs but no accessibility scans

Known gaps (trade / chart / wallet focus)

Gap Why it matters Where
Canvas chart has no AT summary Screen readers get silent plot when candles render (canvas hidden) — only empty state is labeled PriceChart / PriceChartLightweightCanvas
Headline price not announced “Last” USD price updates on tape/interval but is visual + title only PriceChart data-testid="trade-chart-headline-price"
Interval toggles Buttons show 1m text but no aria-pressed name beyond visual state for some AT combos PriceChart interval row
Chart panel landmark No region / aria-labelledby tying controls to “Price chart for {pair}” PriceChart, ChartsPage.tsx
Order book semantics Grid of limits is not exposed as table or listbox; depth % bars are decorative without text alternative OrderBookPanel
Market vs limit tab panels Tabs exist; verify each panel has role="tabpanel" + aria-labelledby wiring TradeOrderTicket
Wallet menu focus trap Escape closes menu (#187) but no roving tabindex / focus return documented WalletButton
Connect modal Modal.tsx focuses panel on open; no focus trap or aria-describedby for connect errors WalletModal + Modal
Live status scatter Some errors use role="alert"; others role="status" on non-errors (e.g. ticket mutation block) — inconsistent priority TradeOrderTicket, TradeMarketOrderPanel
Residual unlabeled inputs #143 closed but spot-check trade/limit paths when extending a11y CI TradeOrderTicket, LimitOrdersPage, escrow field

Testing today


Why this is needed

  1. Regulatory / retail trust — DEX users include keyboard-only and screen-reader operators; silent charts and unlabeled book rows block fair access to price and liquidity data.
  2. Regression velocity — Custom widgets (lightweight-charts canvas, portal listboxes, wallet menus) break easily; without CI, each PR re-audits manually.
  3. Gap closure — GAP_1780023683 explicitly rates a11y Partial and lists axe-playwright on critical routes as P3 work that should graduate to enforced quality bar.
  4. Incomplete prior sweep — #143 fixed many form labels but did not cover chart summaries, book semantics, wallet focus trap, or automated scanning.
  5. Align with documented invariants — docs/frontend.md already states chart empty-state and wallet aria-label rules; CI should enforce the same class of checks on /trade, /charts, and header wallet flows.

Constraints and guardrails

  1. Canvas stays aria-hidden — Do not expose raw canvas pixels to AT; provide textual summary (aria-live region, visually hidden list, or role="region" with headline + interval + last price) per docs/frontend.md § Trade page — price chart.
  2. No hosted TradingView widget — lightweight-charts only; a11y work must not assume iframe embed APIs.
  3. E2E policy — Follow #201: axe scans run in e2e-smoke (or dedicated e2e-a11y project) with VITE_PLAYWRIGHT_E2E=true, risk modal suppressed; no new test.skip without issue link.
  4. Playwright workers — Keep 5 workers in playwright.config.ts unless stability review says otherwise (.cursor/rules/playwright-workers.mdc).
  5. axe scope — Start with critical routes: /trade (default pair), /charts, connected-wallet header (smoke connect or mock store if needed). Exclude full tx projects initially to avoid LocalTerra flake coupling.
  6. WCAG target — Gate on WCAG 2.1 AA via axe tags: ['wcag2a', 'wcag2aa']; document any intentional exceptions in test file comments + docs/frontend.md.
  7. eslint jsx-a11y — If added, use warn → error in CI only after baseline clean; do not blanket-disable rules on PriceChartLightweightCanvas without scoped override.
  8. Sounds / haptics — a11y fixes must not remove sounds.playButtonPress() or existing data-testid hooks used by E2E.
  9. i18n — Strings remain English inline (no i18n framework); new aria-label copy should match visible labels.
  10. Third-party chart — Do not fork lightweight-charts; wrap at React layer only.

Relevant files

Area Path
Trade page shell frontend-dapp/src/pages/TradePage.tsx, TradePage.test.tsx
Order ticket / market frontend-dapp/src/components/trade/TradeOrderTicket.tsx, TradeMarketOrderPanel.tsx
Order book frontend-dapp/src/components/trade/OrderBookPanel.tsx, OrderBookPanel.test.tsx
Chart frontend-dapp/src/components/charts/PriceChart.tsx, PriceChartOverlayMenu.tsx, PriceChartLightweightCanvas.tsx, PriceChartEmptyState.tsx, frontend-dapp/src/pages/ChartsPage.tsx
Wallet frontend-dapp/src/components/wallet/WalletButton.tsx, WalletModal.tsx, WalletDropdownMenuItems.tsx, WalletLuncBalance.tsx
Shared modal frontend-dapp/src/components/ui/Modal.tsx
E2E frontend-dapp/e2e/navigation.spec.ts, new frontend-dapp/e2e/a11y-*.spec.ts
Config frontend-dapp/playwright.config.ts, frontend-dapp/package.json, frontend-dapp/eslint.config.js
Docs docs/frontend.md, docs/qa-templates/qa-test-pass.md
Skills (add or extend) skills/AGENTS_FRONTEND_A11Y_FORM_LABELS.md, skills/AGENTS_FRONTEND_A11Y_FOCUS.md
Gap gaps/GAP_1780023683.md

Phase A — Inventory + fixes (trade / chart / wallet)

  1. Run axe DevTools or @axe-core/playwright locally on /trade, /charts, wallet open/closed; export violation list into issue comment or docs/frontend.md appendix.
  2. Chart: Add section / role="region" with aria-labelledby pointing at “Price (USD)” heading; add sr-only or aria-live="polite" summary for interval + last price + loading state; ensure interval buttons have discernible names (aria-label={${iv} interval} if needed).
  3. Order book: Either (a) promote to <table> with <th scope="col"> for Price/Size/Total, or (b) keep list but add aria-label on each row summarizing side/price/size and expose column headers via id + headers / aria-describedby.
  4. Trade ticket: Wire tabpanel / aria-controls for limit vs market panels; audit role="status" vs role="alert" for tx errors.
  5. Wallet: On menu open, move focus to first menuitem or menu container; on close, return focus to trigger; optional focus trap inside Modal for connect flow (coordinate with risk modal dismissible={false} behavior).

Phase B — Automation

  1. Add @axe-core/playwright devDependency; helper assertNoCriticalA11yViolations(page, { include, exclude }).
  2. New spec e2e/a11y-critical-routes.spec.ts: scan /trade, /charts, wallet menu (use existing connect helpers or PLAYWRIGHT_SKIP_CHAIN=1 smoke with disconnected state where valid).
  3. Optional: eslint-plugin-jsx-a11y with recommended rules; fix baseline in trade/chart/wallet dirs first.
  4. Optional Vitest: jest-axe on rendered PriceChart empty + WalletButton disconnected — complements but does not replace route-level axe.

Phase C — Docs

  1. New docs/frontend.md § Accessibility CI — routes scanned, how to run locally (npm run test:e2e -- e2e/a11y-critical-routes.spec.ts), exception process.
  2. New skill skills/AGENTS_FRONTEND_A11Y_CI.md for agents.

Acceptance criteria

  • Documented axe baseline: zero critical/serious violations on /trade and /charts at default viewport (1280×720), and on header wallet connect menu (disconnected + connected states as feasible in smoke).
  • Chart with live candles exposes a non-hidden text summary of interval + last price (and loading/refetch state) without removing aria-hidden from canvas.
  • Order book exposes programmatic structure (table or labeled list) so price/size/total columns are identifiable to AT.
  • Trade order type tabs: tablist + tab + tabpanel association verified (limit and market panels).
  • Wallet dropdown: focus moves into menu on open and returns to trigger on Escape/close; dismiss control remains labeled.
  • @axe-core/playwright (or equivalent) runs in CI frontend E2E job on critical routes; failures block merge.
  • docs/frontend.md updated with a11y CI invariants; gap row in gaps/GAP_1780023683.md → Improved or Done.
  • New/updated Vitest assertions for at least one trade, one chart, and one wallet a11y contract per merged fix.

Test plan — functional paths

# Path Steps Expected
1 Trade page axe Open /trade with default pair, wait for chart shell axe: no critical/serious; chart region has accessible name
2 Chart interval switch Change 1h → 15m Summary/live region reflects interval; no new axe violations
3 Chart empty Pair with no candles (local/mock) Empty state aria-label present; axe clean
4 Chart fullscreen Expand → Exit aria-pressed toggles; focus not lost irrecoverably
5 Charts page /charts select pair Same chart a11y as trade; pair filter labeled (#143 pattern)
6 Order book keyboard Tab to book row actions Edit/Cancel buttons focusable with visible ring (#144)
7 Limit ticket tabs Switch limit ↔ market Selected tab aria-selected; panel content associated
8 Wallet disconnected Open connect from header Modal role="dialog" + title; wallet options named
9 Wallet connected Open menu → Copy address Menu items reachable; Escape closes; focus returns
10 Mobile nav + wallet 375px viewport navigation.spec.ts band No overlap; wallet still operable (regression)
11 Indexer outage banner Force chart/book unavailable role="alert" or status messages announced; axe still passes or documented exception
12 CI Push branch test:e2e (or a11y project) green with axe step

Automated: npm run test:run (Vitest); npm run test:e2e -- e2e/a11y-critical-routes.spec.ts (and full smoke on release candidates).


Test plan — attack / abuse vectors

Accessibility fixes must not weaken security or mislead users about chain state.

# Vector Mitigation to verify
A1 Misleading live region — AT announces stale price after pair switch Summary resets on pairAddress change; no announcement of prior pair’s last price
A2 Hidden actionable controls — axe “pass” via aria-hidden on real buttons Scan excludes only canvas/decorative depth bars, not interactive nodes
A3 Focus trap escape — User cannot reach browser chrome / disconnect Escape and backdrop still close wallet menu; connect modal blocking mode unchanged for risk modal
A4 Phishing via aria-label — Labels contradict visible network Wallet trigger label still uses getNetworkBadgeCopy() short label (#186)
A5 Screen reader spam — aria-live fires every 30s refetch Polite region debounced or updates only on meaningful price/interval change
A6 Book row action confusion — Edit prefills wrong order aria-label includes order_id; prefill E2E unchanged
A7 axe rule suppression abuse — Broad disableRules in CI Each disabled rule requires comment + docs exception; max 0 for critical routes at merge
A8 E2E false green — Scan disconnected-only while connected UI differs CI runs connected scan when chain available; document smoke-only gap if PLAYWRIGHT_SKIP_CHAIN=1

Verification criteria

  • All acceptance criteria checked.
  • frontend-dapp Vitest green; new tests reference this issue IID in describe names or comments.
  • npm run test:e2e includes axe spec green on CI (or new e2e-a11y project documented in docs/frontend.md).
  • Manual spot-check: VoiceOver (macOS) or NVDA (Windows) on /trade — hears chart summary + interval; tab through order book actions.
  • rg 'axe-core|@axe-core/playwright' frontend-dapp/package.json shows devDependency; no duplicate competing scanners without justification.
  • Gap inventory Accessibility row updated in gaps/GAP_1780023683.md.

Labels / metadata

  • Labels: frontend, QA
  • Owner type: frontend
  • Priority: P2 (enforcement) / P3 (full-site expansion after critical routes)
  • Dependencies: #143, #144, #181, #186, #201; does not change contracts or indexer.
## Summary Close the **partial accessibility** gap on retail-critical surfaces (**trade**, **price chart**, **wallet**): finish missing `aria-*` / programmatic names / live regions where canvas and custom widgets hide meaning from assistive tech, and add a **repeatable audit gate** (`@axe-core/playwright` and/or `eslint-plugin-jsx-a11y`) so regressions fail CI instead of relying on ad-hoc source review. Tracked in gap inventory: `gaps/GAP_1780023683.md` — *Accessibility* (**Partial**: some `aria-*` on trade/chart/wallet; no systematic a11y audit or axe in CI). Product backlog: same doc § P3 — *a11y CI — axe-playwright on critical routes*. Builds on closed label sweep ([#143](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/143)), focus rings ([#144](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/144)), wallet chip ([#186](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/186)), portal listbox ([#181](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/181)), and chart empty-state docs ([#151](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/151)). --- ## Current codebase ### What already exists | Area | Coverage | Representative files | |------|----------|----------------------| | **Shell / nav** | Strong | [`Layout.tsx`](frontend-dapp/src/components/common/Layout.tsx) — `aria-label` on nav/menus, `aria-expanded` / `role="menu"` | | **Trade ticket** | Partial–good | [`TradeOrderTicket.tsx`](frontend-dapp/src/components/trade/TradeOrderTicket.tsx) — `role="tablist"` / `aria-selected` for limit vs market; [`LimitOrderBidAskSideSelector.tsx`](frontend-dapp/src/components/trade/LimitOrderBidAskSideSelector.tsx) — `radiogroup` + `aria-checked`; [`LimitOrderPreSubmitSummary.tsx`](frontend-dapp/src/components/trade/LimitOrderPreSubmitSummary.tsx) — `role="region"` | | **Order book** | Partial | [`OrderBookPanel.tsx`](frontend-dapp/src/components/trade/OrderBookPanel.tsx) — per-row `aria-label` on Edit/Cancel; book is a **`<ul>` grid**, not a semantic `<table>`; column headers are visual `<span>`s only | | **Chart** | Partial | [`PriceChart.tsx`](frontend-dapp/src/components/charts/PriceChart.tsx) — interval `role="group"`, fullscreen `aria-pressed` / `aria-label`, interval-loading `aria-live`; [`PriceChartOverlayMenu.tsx`](frontend-dapp/src/components/charts/PriceChartOverlayMenu.tsx) — `aria-expanded` / `aria-controls`; [`PriceChartLightweightCanvas.tsx`](frontend-dapp/src/components/charts/PriceChartLightweightCanvas.tsx) — canvas **`aria-hidden`** (correct — plot is decorative to AT); [`PriceChartEmptyState.tsx`](frontend-dapp/src/components/charts/PriceChartEmptyState.tsx) — `role="img"` + `aria-label` | | **Wallet** | Partial–good | [`WalletButton.tsx`](frontend-dapp/src/components/wallet/WalletButton.tsx) — connected trigger `aria-label` includes network; menu `role="menu"` / `menuitem`; dismiss backdrop labeled; [`WalletModal.tsx`](frontend-dapp/src/components/wallet/WalletModal.tsx) — per-option `aria-label` with extension detection; [`WalletLuncBalance.tsx`](frontend-dapp/src/components/wallet/WalletLuncBalance.tsx) — `aria-busy` + `sr-only` loading | | **Docs / agents** | Partial | [`docs/frontend.md`](docs/frontend.md) § form labels, focus visible, chart empty a11y; [`skills/AGENTS_FRONTEND_A11Y_FORM_LABELS.md`](skills/AGENTS_FRONTEND_A11Y_FORM_LABELS.md), [`skills/AGENTS_FRONTEND_A11Y_FOCUS.md`](skills/AGENTS_FRONTEND_A11Y_FOCUS.md) | | **Automated a11y** | **None** | No `axe-core`, `@axe-core/playwright`, `jest-axe`, or `eslint-plugin-jsx-a11y` in [`frontend-dapp/package.json`](frontend-dapp/package.json); Playwright E2E ([`playwright.config.ts`](frontend-dapp/playwright.config.ts), 5 workers) has navigation/wallet specs but **no accessibility scans** | ### Known gaps (trade / chart / wallet focus) | Gap | Why it matters | Where | |-----|----------------|-------| | **Canvas chart has no AT summary** | Screen readers get silent plot when candles render (canvas hidden) — only empty state is labeled | `PriceChart` / `PriceChartLightweightCanvas` | | **Headline price not announced** | “Last” USD price updates on tape/interval but is visual + `title` only | `PriceChart` `data-testid="trade-chart-headline-price"` | | **Interval toggles** | Buttons show `1m` text but no `aria-pressed` name beyond visual state for some AT combos | `PriceChart` interval row | | **Chart panel landmark** | No `region` / `aria-labelledby` tying controls to “Price chart for {pair}” | `PriceChart`, [`ChartsPage.tsx`](frontend-dapp/src/pages/ChartsPage.tsx) | | **Order book semantics** | Grid of limits is not exposed as table or listbox; depth % bars are decorative without text alternative | `OrderBookPanel` | | **Market vs limit tab panels** | Tabs exist; verify each panel has `role="tabpanel"` + `aria-labelledby` wiring | `TradeOrderTicket` | | **Wallet menu focus trap** | Escape closes menu ([#187](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/187)) but no roving tabindex / focus return documented | `WalletButton` | | **Connect modal** | [`Modal.tsx`](frontend-dapp/src/components/ui/Modal.tsx) focuses panel on open; no focus trap or `aria-describedby` for connect errors | `WalletModal` + `Modal` | | **Live status scatter** | Some errors use `role="alert"`; others `role="status"` on non-errors (e.g. ticket mutation block) — inconsistent priority | `TradeOrderTicket`, `TradeMarketOrderPanel` | | **Residual unlabeled inputs** | [#143](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/143) closed but spot-check trade/limit paths when extending a11y CI | `TradeOrderTicket`, `LimitOrdersPage`, escrow field | ### Testing today - **Vitest + Testing Library** assert **spot** `aria-*` on components (e.g. [`LimitOrderBidAskSideSelector.test.tsx`](frontend-dapp/src/components/trade/__tests__/LimitOrderBidAskSideSelector.test.tsx), [`WalletLuncBalance.test.tsx`](frontend-dapp/src/components/wallet/__tests__/WalletLuncBalance.test.tsx), [`PriceChart.test.tsx`](frontend-dapp/src/components/charts/__tests__/PriceChart.test.tsx) empty state). - **Playwright** (`e2e/navigation.spec.ts`, trade specs) — layout/functional only. - **QA template** [`docs/qa-templates/qa-test-pass.md`](docs/qa-templates/qa-test-pass.md) mentions slippage accessibility in passing — not enforced. --- ## Why this is needed 1. **Regulatory / retail trust** — DEX users include keyboard-only and screen-reader operators; silent charts and unlabeled book rows block fair access to price and liquidity data. 2. **Regression velocity** — Custom widgets (lightweight-charts canvas, portal listboxes, wallet menus) break easily; without CI, each PR re-audits manually. 3. **Gap closure** — `GAP_1780023683` explicitly rates a11y **Partial** and lists **axe-playwright on critical routes** as P3 work that should graduate to enforced quality bar. 4. **Incomplete prior sweep** — [#143](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/143) fixed many form labels but did not cover chart summaries, book semantics, wallet focus trap, or automated scanning. 5. **Align with documented invariants** — `docs/frontend.md` already states chart empty-state and wallet `aria-label` rules; CI should enforce the same class of checks on `/trade`, `/charts`, and header wallet flows. --- ## Constraints and guardrails 1. **Canvas stays `aria-hidden`** — Do not expose raw canvas pixels to AT; provide **textual summary** (`aria-live` region, visually hidden list, or `role="region"` with headline + interval + last price) per [`docs/frontend.md` § Trade page — price chart](docs/frontend.md#trade-page-price-chart-invariants). 2. **No hosted TradingView widget** — lightweight-charts only; a11y work must not assume iframe embed APIs. 3. **E2E policy** — Follow [#201](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/201): axe scans run in **`e2e-smoke`** (or dedicated `e2e-a11y` project) with `VITE_PLAYWRIGHT_E2E=true`, risk modal suppressed; **no new `test.skip` without issue link**. 4. **Playwright workers** — Keep **5 workers** in `playwright.config.ts` unless stability review says otherwise (`.cursor/rules/playwright-workers.mdc`). 5. **axe scope** — Start with **critical routes**: `/trade` (default pair), `/charts`, connected-wallet header (smoke connect or mock store if needed). Exclude full tx projects initially to avoid LocalTerra flake coupling. 6. **WCAG target** — Gate on **WCAG 2.1 AA** via axe `tags: ['wcag2a', 'wcag2aa']`; document any **intentional exceptions** in test file comments + `docs/frontend.md`. 7. **eslint jsx-a11y** — If added, use **warn → error** in CI only after baseline clean; do not blanket-disable rules on `PriceChartLightweightCanvas` without scoped override. 8. **Sounds / haptics** — a11y fixes must not remove `sounds.playButtonPress()` or existing `data-testid` hooks used by E2E. 9. **i18n** — Strings remain English inline (no i18n framework); new `aria-label` copy should match visible labels. 10. **Third-party chart** — Do not fork lightweight-charts; wrap at React layer only. --- ## Relevant files | Area | Path | |------|------| | Trade page shell | `frontend-dapp/src/pages/TradePage.tsx`, `TradePage.test.tsx` | | Order ticket / market | `frontend-dapp/src/components/trade/TradeOrderTicket.tsx`, `TradeMarketOrderPanel.tsx` | | Order book | `frontend-dapp/src/components/trade/OrderBookPanel.tsx`, `OrderBookPanel.test.tsx` | | Chart | `frontend-dapp/src/components/charts/PriceChart.tsx`, `PriceChartOverlayMenu.tsx`, `PriceChartLightweightCanvas.tsx`, `PriceChartEmptyState.tsx`, `frontend-dapp/src/pages/ChartsPage.tsx` | | Wallet | `frontend-dapp/src/components/wallet/WalletButton.tsx`, `WalletModal.tsx`, `WalletDropdownMenuItems.tsx`, `WalletLuncBalance.tsx` | | Shared modal | `frontend-dapp/src/components/ui/Modal.tsx` | | E2E | `frontend-dapp/e2e/navigation.spec.ts`, new `frontend-dapp/e2e/a11y-*.spec.ts` | | Config | `frontend-dapp/playwright.config.ts`, `frontend-dapp/package.json`, `frontend-dapp/eslint.config.js` | | Docs | `docs/frontend.md`, `docs/qa-templates/qa-test-pass.md` | | Skills (add or extend) | `skills/AGENTS_FRONTEND_A11Y_FORM_LABELS.md`, `skills/AGENTS_FRONTEND_A11Y_FOCUS.md` | | Gap | `gaps/GAP_1780023683.md` | --- ## Recommended direction ### Phase A — Inventory + fixes (trade / chart / wallet) 1. Run **axe DevTools** or `@axe-core/playwright` locally on `/trade`, `/charts`, wallet open/closed; export violation list into issue comment or `docs/frontend.md` appendix. 2. **Chart**: Add `section` / `role="region"` with `aria-labelledby` pointing at “Price (USD)” heading; add **`sr-only` or `aria-live="polite"`** summary for interval + last price + loading state; ensure interval buttons have discernible names (`aria-label={`${iv} interval`}` if needed). 3. **Order book**: Either (a) promote to `<table>` with `<th scope="col">` for Price/Size/Total, or (b) keep list but add `aria-label` on each row summarizing side/price/size and expose column headers via `id` + `headers` / `aria-describedby`. 4. **Trade ticket**: Wire `tabpanel` / `aria-controls` for limit vs market panels; audit `role="status"` vs `role="alert"` for tx errors. 5. **Wallet**: On menu open, move focus to first `menuitem` or menu container; on close, return focus to trigger; optional focus trap inside `Modal` for connect flow (coordinate with risk modal `dismissible={false}` behavior). ### Phase B — Automation 1. Add **`@axe-core/playwright`** devDependency; helper `assertNoCriticalA11yViolations(page, { include, exclude })`. 2. New spec **`e2e/a11y-critical-routes.spec.ts`**: scan `/trade`, `/charts`, wallet menu (use existing connect helpers or `PLAYWRIGHT_SKIP_CHAIN=1` smoke with disconnected state where valid). 3. Optional: **`eslint-plugin-jsx-a11y`** with recommended rules; fix baseline in trade/chart/wallet dirs first. 4. Optional Vitest: **`jest-axe`** on rendered `PriceChart` empty + `WalletButton` disconnected — complements but does not replace route-level axe. ### Phase C — Docs 1. New `docs/frontend.md` § **Accessibility CI** — routes scanned, how to run locally (`npm run test:e2e -- e2e/a11y-critical-routes.spec.ts`), exception process. 2. New skill **`skills/AGENTS_FRONTEND_A11Y_CI.md`** for agents. --- ## Acceptance criteria - [ ] Documented axe baseline: **zero critical/serious** violations on `/trade` and `/charts` at default viewport (1280×720), and on header wallet connect menu (disconnected + connected states as feasible in smoke). - [ ] Chart with live candles exposes a **non-hidden text summary** of interval + last price (and loading/refetch state) without removing `aria-hidden` from canvas. - [ ] Order book exposes **programmatic structure** (table or labeled list) so price/size/total columns are identifiable to AT. - [ ] Trade order type tabs: **tablist + tab + tabpanel** association verified (limit and market panels). - [ ] Wallet dropdown: **focus moves into menu on open** and returns to trigger on Escape/close; dismiss control remains labeled. - [ ] `@axe-core/playwright` (or equivalent) runs in **CI frontend E2E job** on critical routes; failures block merge. - [ ] `docs/frontend.md` updated with a11y CI invariants; gap row in `gaps/GAP_1780023683.md` → **Improved** or **Done**. - [ ] New/updated Vitest assertions for at least one trade, one chart, and one wallet a11y contract per merged fix. --- ## Test plan — functional paths | # | Path | Steps | Expected | |---|------|-------|----------| | 1 | Trade page axe | Open `/trade` with default pair, wait for chart shell | axe: no critical/serious; chart region has accessible name | | 2 | Chart interval switch | Change `1h` → `15m` | Summary/live region reflects interval; no new axe violations | | 3 | Chart empty | Pair with no candles (local/mock) | Empty state `aria-label` present; axe clean | | 4 | Chart fullscreen | Expand → Exit | `aria-pressed` toggles; focus not lost irrecoverably | | 5 | Charts page | `/charts` select pair | Same chart a11y as trade; pair filter labeled ([#143](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/143) pattern) | | 6 | Order book keyboard | Tab to book row actions | Edit/Cancel buttons focusable with visible ring ([#144](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/144)) | | 7 | Limit ticket tabs | Switch limit ↔ market | Selected tab `aria-selected`; panel content associated | | 8 | Wallet disconnected | Open connect from header | Modal `role="dialog"` + title; wallet options named | | 9 | Wallet connected | Open menu → Copy address | Menu items reachable; Escape closes; focus returns | | 10 | Mobile nav + wallet | 375px viewport `navigation.spec.ts` band | No overlap; wallet still operable (regression) | | 11 | Indexer outage banner | Force chart/book unavailable | `role="alert"` or status messages announced; axe still passes or documented exception | | 12 | CI | Push branch | `test:e2e` (or a11y project) green with axe step | **Automated:** `npm run test:run` (Vitest); `npm run test:e2e -- e2e/a11y-critical-routes.spec.ts` (and full smoke on release candidates). --- ## Test plan — attack / abuse vectors Accessibility fixes must not weaken security or mislead users about chain state. | # | Vector | Mitigation to verify | |---|--------|---------------------| | A1 | **Misleading live region** — AT announces stale price after pair switch | Summary resets on `pairAddress` change; no announcement of prior pair’s last price | | A2 | **Hidden actionable controls** — axe “pass” via `aria-hidden` on real buttons | Scan excludes only canvas/decorative depth bars, not interactive nodes | | A3 | **Focus trap escape** — User cannot reach browser chrome / disconnect | Escape and backdrop still close wallet menu; connect modal blocking mode unchanged for risk modal | | A4 | **Phishing via `aria-label`** — Labels contradict visible network | Wallet trigger label still uses `getNetworkBadgeCopy()` short label ([#186](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/186)) | | A5 | **Screen reader spam** — `aria-live` fires every 30s refetch | Polite region debounced or updates only on meaningful price/interval change | | A6 | **Book row action confusion** — Edit prefills wrong order | `aria-label` includes `order_id`; prefill E2E unchanged | | A7 | **axe rule suppression abuse** — Broad `disableRules` in CI | Each disabled rule requires comment + docs exception; max 0 for critical routes at merge | | A8 | **E2E false green** — Scan disconnected-only while connected UI differs | CI runs connected scan when chain available; document smoke-only gap if `PLAYWRIGHT_SKIP_CHAIN=1` | --- ## Verification criteria - [ ] All acceptance criteria checked. - [ ] `frontend-dapp` Vitest green; new tests reference this issue IID in describe names or comments. - [ ] `npm run test:e2e` includes axe spec green on CI (or new `e2e-a11y` project documented in `docs/frontend.md`). - [ ] Manual spot-check: VoiceOver (macOS) or NVDA (Windows) on `/trade` — hears chart summary + interval; tab through order book actions. - [ ] `rg 'axe-core|@axe-core/playwright' frontend-dapp/package.json` shows devDependency; no duplicate competing scanners without justification. - [ ] Gap inventory `Accessibility` row updated in `gaps/GAP_1780023683.md`. --- ## Labels / metadata - **Labels:** `frontend`, `QA` - **Owner type:** frontend - **Priority:** P2 (enforcement) / P3 (full-site expansion after critical routes) - **Dependencies:** [#143](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/143), [#144](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/144), [#181](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/181), [#186](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/186), [#201](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/201); does not change contracts or indexer.
PlasticDigits commented 2026-05-29 03:17:55 +00:00 (Migrated from gitlab.com)

mentioned in issue #215

mentioned in issue #215
PlasticDigits commented 2026-05-29 03:35:03 +00:00 (Migrated from gitlab.com)

mentioned in commit 99f4097b28

mentioned in commit 99f4097b2857929d5b882df415717dccd2fcec33
PlasticDigits commented 2026-05-29 03:35:04 +00:00 (Migrated from gitlab.com)

mentioned in commit e02d991ce9

mentioned in commit e02d991ce9169189255107d8965ec77f5e2f0c85
PlasticDigits commented 2026-05-29 03:35:12 +00:00 (Migrated from gitlab.com)

Implementation summary (GitLab #214)

Merged to main in e02d991 — systematic a11y on trade/chart/wallet plus an axe CI gate.

What changed

  • Price chart: role="region" with labelled heading + aria-live text summary (interval, last price, loading); interval buttons get aria-label; canvas stays aria-hidden.
  • Order book: per-side <table> with column headers; row aria-label includes side, order id, price, size.
  • Trade ticket: limit/market tabpanel wiring; pair-paused banner uses role="alert".
  • Wallet: focus moves to first menuitem on open and returns to trigger on close; role="menu" contains menuitems only (header outside); connect button aria-label.
  • Modal: focus trap on dismissible dialogs.
  • CI: @axe-core/playwright + e2e/a11y-critical-routes.spec.ts in e2e-smoke (WCAG 2.1 A/AA, critical/serious gate).
  • Docs: docs/frontend.md § Accessibility CI, skills/AGENTS_FRONTEND_A11Y_CI.md, gap row updated in gaps/GAP_1780023683.md.

Verification checklist

  • cd frontend-dapp && npm run test:run — Vitest green (chart, order book, wallet a11y tests).
  • PLAYWRIGHT_SKIP_CHAIN=1 npm run test:e2e:smoke -- e2e/a11y-critical-routes.spec.ts — axe smoke passes locally.
  • CI e2e-smoke job includes axe spec on merge pipeline.
  • /trade with indexer up: VoiceOver/NVDA hears chart summary on interval change; canvas not exposed.
  • Order book: screen reader identifies Price / Size / Total columns.
  • Limit ↔ Market tabs: selected state + panel association.
  • Connected wallet: open menu → focus in menu → Escape → focus on trigger.
  • Connect wallet modal: dialog title + no critical axe violations in dialog scope.

@brouie — please run through the checklist when you have a moment; leaving the issue open until verified.

## Implementation summary (GitLab #214) Merged to `main` in **e02d991** — systematic a11y on trade/chart/wallet plus an axe CI gate. ### What changed - **Price chart:** `role="region"` with labelled heading + `aria-live` text summary (interval, last price, loading); interval buttons get `aria-label`; canvas stays `aria-hidden`. - **Order book:** per-side `<table>` with column headers; row `aria-label` includes side, order id, price, size. - **Trade ticket:** limit/market `tabpanel` wiring; pair-paused banner uses `role="alert"`. - **Wallet:** focus moves to first menuitem on open and returns to trigger on close; `role="menu"` contains menuitems only (header outside); connect button `aria-label`. - **Modal:** focus trap on dismissible dialogs. - **CI:** `@axe-core/playwright` + `e2e/a11y-critical-routes.spec.ts` in **e2e-smoke** (WCAG 2.1 A/AA, critical/serious gate). - **Docs:** [docs/frontend.md § Accessibility CI](docs/frontend.md#accessibility-ci), `skills/AGENTS_FRONTEND_A11Y_CI.md`, gap row updated in `gaps/GAP_1780023683.md`. ### Verification checklist - [ ] `cd frontend-dapp && npm run test:run` — Vitest green (chart, order book, wallet a11y tests). - [ ] `PLAYWRIGHT_SKIP_CHAIN=1 npm run test:e2e:smoke -- e2e/a11y-critical-routes.spec.ts` — axe smoke passes locally. - [ ] CI **e2e-smoke** job includes axe spec on merge pipeline. - [ ] `/trade` with indexer up: VoiceOver/NVDA hears chart summary on interval change; canvas not exposed. - [ ] Order book: screen reader identifies Price / Size / Total columns. - [ ] Limit ↔ Market tabs: selected state + panel association. - [ ] Connected wallet: open menu → focus in menu → Escape → focus on trigger. - [ ] Connect wallet modal: dialog title + no critical axe violations in dialog scope. @brouie — please run through the checklist when you have a moment; leaving the issue **open** until verified.
PlasticDigits commented 2026-05-29 05:41:31 +00:00 (Migrated from gitlab.com)

mentioned in issue #228

mentioned in issue #228
PlasticDigits commented 2026-05-29 05:41:35 +00:00 (Migrated from gitlab.com)

marked as related to #228

marked as related to #228
PlasticDigits commented 2026-05-29 15:37:44 +00:00 (Migrated from gitlab.com)

mentioned in commit 13fef659ea

mentioned in commit 13fef659ea614009286cc85a1d1e352af19d37ae
PlasticDigits commented 2026-05-29 15:37:53 +00:00 (Migrated from gitlab.com)

Verification report (agent — GitLab #214)

Worktree: verify/issue-214 → merged to main as 13fef65.

What was already on main (verified)

  • @axe-core/playwright in e2e-smoke via e2e/a11y-critical-routes.spec.ts (WCAG 2.1 A+AA, zero critical/serious gate)
  • Chart: role="region", aria-live polite summary, interval aria-label / aria-pressed
  • Order book: <table> + <th scope="col">, row aria-label with order id
  • Trade ticket: tablist / tab / tabpanel wiring
  • Wallet: focus into menu on open, return to trigger on Escape
  • Docs: docs/frontend.md § Accessibility CI, skills/AGENTS_FRONTEND_A11Y_CI.md, gap row Improved in gaps/GAP_1780023683.md
  • Vitest: PriceChart.test.tsx, OrderBookPanel.test.tsx, WalletButton.test.tsx (#214 contracts)

Fix applied during verification

/charts axe failure (aria-hidden-focus): lightweight-charts injects focusable #tv-attr-logo inside aria-hidden canvas wrapper.

  • Set layout.attributionLogo: false in PriceChartLightweightCanvas
  • Added accessible Charting by TradingView link on PriceChart (data-testid="price-chart-tradingview-attribution")
  • Documented in docs/frontend.md, AGENTS_FRONTEND_A11Y_CI.md, AGENTS_FRONTEND_PRICE_CHART.md

Automated runs (pass)

  • npm run test:run — PriceChart, OrderBookPanel, WalletButton, PriceChartLightweightCanvas
  • PLAYWRIGHT_SKIP_CHAIN=1 npm run test:e2e:smoke -- e2e/a11y-critical-routes.spec.ts (4/4)
  • npm run test:e2e:smoke -- e2e/a11y-critical-routes.spec.ts with LocalTerra/indexer up (4/4)
  • Browser spot-check: trade page exposes region "PRICE (USD)" and live summary text (loading/outage/interval)

Checklist for human follow-up

  • VoiceOver / NVDA on /trade: hear chart summary + interval; tab order book Edit/Cancel
  • Confirm Charting by TradingView link visible when candles render (/trade + /charts)
  • Interval switch 1h → 15m: summary updates; re-run axe locally if desired
  • Wallet connected menu: Copy address, Escape → focus on trigger
  • CI pipeline green on main after 13fef65

Not closed

Manual AT spot-check in verification criteria was not performed in this agent session. @brouie — please run NVDA/VoiceOver item above; if pass, close #214.

## Verification report (agent — GitLab #214) Worktree: `verify/issue-214` → merged to `main` as **13fef65**. ### What was already on `main` (verified) - `@axe-core/playwright` in `e2e-smoke` via `e2e/a11y-critical-routes.spec.ts` (WCAG 2.1 A+AA, zero critical/serious gate) - Chart: `role="region"`, `aria-live` polite summary, interval `aria-label` / `aria-pressed` - Order book: `<table>` + `<th scope="col">`, row `aria-label` with order id - Trade ticket: `tablist` / `tab` / `tabpanel` wiring - Wallet: focus into menu on open, return to trigger on Escape - Docs: `docs/frontend.md` § Accessibility CI, `skills/AGENTS_FRONTEND_A11Y_CI.md`, gap row **Improved** in `gaps/GAP_1780023683.md` - Vitest: `PriceChart.test.tsx`, `OrderBookPanel.test.tsx`, `WalletButton.test.tsx` (#214 contracts) ### Fix applied during verification **`/charts` axe failure** (`aria-hidden-focus`): lightweight-charts injects focusable `#tv-attr-logo` inside `aria-hidden` canvas wrapper. - Set `layout.attributionLogo: false` in `PriceChartLightweightCanvas` - Added accessible **Charting by TradingView** link on `PriceChart` (`data-testid="price-chart-tradingview-attribution"`) - Documented in `docs/frontend.md`, `AGENTS_FRONTEND_A11Y_CI.md`, `AGENTS_FRONTEND_PRICE_CHART.md` ### Automated runs (pass) - [x] `npm run test:run` — PriceChart, OrderBookPanel, WalletButton, PriceChartLightweightCanvas - [x] `PLAYWRIGHT_SKIP_CHAIN=1 npm run test:e2e:smoke -- e2e/a11y-critical-routes.spec.ts` (4/4) - [x] `npm run test:e2e:smoke -- e2e/a11y-critical-routes.spec.ts` with LocalTerra/indexer up (4/4) - [x] Browser spot-check: trade page exposes **region "PRICE (USD)"** and live summary text (loading/outage/interval) ### Checklist for human follow-up - [ ] **VoiceOver / NVDA** on `/trade`: hear chart summary + interval; tab order book Edit/Cancel - [ ] Confirm **Charting by TradingView** link visible when candles render (`/trade` + `/charts`) - [ ] Interval switch `1h` → `15m`: summary updates; re-run axe locally if desired - [ ] Wallet connected menu: Copy address, Escape → focus on trigger - [ ] CI pipeline green on `main` after **13fef65** ### Not closed Manual AT spot-check in verification criteria was **not** performed in this agent session. @brouie — please run NVDA/VoiceOver item above; if pass, close #214.
PlasticDigits commented 2026-05-30 09:24:21 +00:00 (Migrated from gitlab.com)

Verification report (agent — requested as #2174; mapped to #214)

Note: GitLab issue #2174 returns 404 on this project. The three checklist items in your prompt match the open human follow-up on #214 (a11y CI). Verification used worktree verify/issue-214 → merged to main as e0a3c9f.

Focused checklist (your three items)

Item Result Evidence
Charting by TradingView visible when candles render (/trade + /charts) Pass Browser on local stack: link data-testid="price-chart-tradingview-attribution" visible with canvas on both routes. Vitest asserts visible + href.
Interval 1h → 15m updates aria-live summary Pass Browser: summary Interval 1h → Interval 15m after click. New Vitest: updates aria-live summary when switching 1h to 15m (#214).
Wallet connected menu: Copy address, Escape → focus on trigger Pass Vitest (#214 contracts). Browser: menu opens with copy menuitem; Escape closes menu; focus returns to connected-wallet trigger (aria-label Connected wallet on Local).

Fix merged during verification

/trade axe scrollable-region-focusable (serious): order book overflow-y-auto panes now use tabIndex={0} + role="region" + aria-label so keyboard users can scroll asks/bids. Documented in docs/frontend.md § Accessibility CI and skills/AGENTS_FRONTEND_A11Y_CI.md.

Automated runs

  • npm run test:run — PriceChart (incl. new interval/attribution tests), OrderBookPanel, WalletButton
  • e2e/a11y-critical-routes.spec.ts — trade + charts axe pass on worktree build (wallet specs need connected dev wallet / chain; skipped-chain wallet click timed out — not a regression on the three UI items above)

Manual follow-up (issue stays open)

  • VoiceOver / NVDA full pass on /trade (chart summary, book columns, tabs) — @brouie
  • UI sound effects (sounds.playButtonPress, etc.) — human only; see sub-checklist below for @totdking

Sub-checklist: sound effects only (@totdking)

Please verify audio feedback only (no need to re-test chart attribution / interval summary / wallet Escape if the three items above already passed for you):

  • Connect wallet → option click plays press sound (if enabled in settings)
  • Trade chart interval buttons → press sound
  • Chart fullscreen expand/exit → press sound
  • Theme toggle → press sound
  • Wallet menu open (chip) → press sound where applicable

Report pass/fail here; tag @brouie if anything blocks closing #214 after AT pass.


Status: Leaving #214 open — NVDA/VoiceOver and sound-effects manual items remain. @brouie — please confirm AT when you can; if the three focused UI items above fail on main after e0a3c9f, reply here.

## Verification report (agent — requested as #2174; mapped to **#214**) **Note:** GitLab issue **#2174** returns 404 on this project. The three checklist items in your prompt match the open human follow-up on [**#214**](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/214) (a11y CI). Verification used worktree `verify/issue-214` → merged to `main` as **e0a3c9f**. ### Focused checklist (your three items) | Item | Result | Evidence | |------|--------|----------| | **Charting by TradingView** visible when candles render (`/trade` + `/charts`) | **Pass** | Browser on local stack: link `data-testid="price-chart-tradingview-attribution"` visible with canvas on both routes. Vitest asserts visible + href. | | **Interval 1h → 15m** updates `aria-live` summary | **Pass** | Browser: summary `Interval 1h` → `Interval 15m` after click. New Vitest: *updates aria-live summary when switching 1h to 15m (#214)*. | | **Wallet connected menu**: Copy address, Escape → focus on trigger | **Pass** | Vitest (#214 contracts). Browser: menu opens with copy menuitem; Escape closes menu; focus returns to connected-wallet trigger (`aria-label` *Connected wallet on Local*). | ### Fix merged during verification **`/trade` axe `scrollable-region-focusable` (serious):** order book `overflow-y-auto` panes now use `tabIndex={0}` + `role="region"` + `aria-label` so keyboard users can scroll asks/bids. Documented in `docs/frontend.md` § Accessibility CI and `skills/AGENTS_FRONTEND_A11Y_CI.md`. ### Automated runs - [x] `npm run test:run` — PriceChart (incl. new interval/attribution tests), OrderBookPanel, WalletButton - [x] `e2e/a11y-critical-routes.spec.ts` — **trade + charts** axe pass on worktree build (wallet specs need connected dev wallet / chain; skipped-chain wallet click timed out — not a regression on the three UI items above) ### Manual follow-up (issue stays open) - [ ] **VoiceOver / NVDA** full pass on `/trade` (chart summary, book columns, tabs) — @brouie - [ ] **UI sound effects** (`sounds.playButtonPress`, etc.) — human only; see sub-checklist below for @totdking --- ### Sub-checklist: sound effects only (@totdking) Please verify **audio feedback only** (no need to re-test chart attribution / interval summary / wallet Escape if the three items above already passed for you): - [ ] Connect wallet → option click plays press sound (if enabled in settings) - [ ] Trade chart interval buttons → press sound - [ ] Chart fullscreen expand/exit → press sound - [ ] Theme toggle → press sound - [ ] Wallet menu open (chip) → press sound where applicable Report pass/fail here; tag @brouie if anything blocks closing #214 after AT pass. --- **Status:** Leaving **#214 open** — NVDA/VoiceOver and sound-effects manual items remain. @brouie — please confirm AT when you can; if the three focused UI items above fail on `main` after **e0a3c9f**, reply here.
PlasticDigits commented 2026-05-30 09:24:39 +00:00 (Migrated from gitlab.com)

mentioned in commit 507ed875cf

mentioned in commit 507ed875cfd21c28b0181d8815b70016f9c47b49
PlasticDigits commented 2026-05-30 09:24:40 +00:00 (Migrated from gitlab.com)

mentioned in commit e0a3c9f02d

mentioned in commit e0a3c9f02d1a33c150fabe68cc49a60f9d8721c3
PlasticDigits commented 2026-05-31 04:41:22 +00:00 (Migrated from gitlab.com)

mentioned in issue #244

mentioned in issue #244
Brouie commented 2026-06-02 17:42:12 +00:00 (Migrated from gitlab.com)

#214 verified — good to close from my side. The manual AT (screen-reader) pass that was left to me is done.

NVDA (Windows) on /trade — all 5 contracts pass:

  • Chart: navigates to region "Price (USD)"; the polite aria-live summary is spoken and re-announces interval + last price on interval switch; the canvas plot itself reads nothing (stays aria-hidden, correct).
  • Order book: exposed as a real table — column headers Price / Size / Total are announced per cell; row Edit/Cancel actions are labeled (side + order id + price).
  • Order type tabs: Limit / Market announce "tab, selected" and switch with panel association (tablist/tab/tabpanel).
  • Wallet menu: focus moves into the menu on open (first menuitem read), and Escape returns focus to the "Connected wallet on Local" trigger.
  • Fullscreen: button announces "Expand chart to fullscreen" / "Exit chart fullscreen"; focus retained.

Automated / source / docs (re-confirmed on current main):

  • a11y Vitest green — 9 files / 88 tests (PriceChart incl. interval+attribution, OrderBookPanel, WalletButton, PriceChartLightweightCanvas).
  • @axe-core/playwright (^4.11.3) + e2e/a11y-critical-routes.spec.ts in e2e-smoke (your agent's axe run was 4/4; trade/charts/wallet, WCAG 2.1 A/AA critical+serious gate).
  • aria contracts present in source: chart role=region + aria-labelledby + aria-live; order book table + th scope=col; tab/tabpanel; wallet focus-in/return; the agent-QA axe fixes (attributionLogo:false, TradingView attribution link, scrollable-region tabindex).
  • docs/frontend.md § Accessibility CI + skills/AGENTS_FRONTEND_A11Y_CI.md; gap row Accessibility -> Improved, backlog a11y CI -> Done.

Remaining: the UI sound-effects sub-checklist is @totdking's (audio feedback, not assistive-tech) — separate from the a11y/AT criteria above.

So all the acceptance + verification criteria in the a11y/AT scope are met. Good to close once @totdking signs off the sounds sub-checklist. @PlasticDigits

#214 verified — good to close from my side. The manual AT (screen-reader) pass that was left to me is done. NVDA (Windows) on /trade — all 5 contracts pass: - [x] Chart: navigates to region "Price (USD)"; the polite aria-live summary is spoken and re-announces interval + last price on interval switch; the canvas plot itself reads nothing (stays aria-hidden, correct). - [x] Order book: exposed as a real table — column headers Price / Size / Total are announced per cell; row Edit/Cancel actions are labeled (side + order id + price). - [x] Order type tabs: Limit / Market announce "tab, selected" and switch with panel association (tablist/tab/tabpanel). - [x] Wallet menu: focus moves into the menu on open (first menuitem read), and Escape returns focus to the "Connected wallet on Local" trigger. - [x] Fullscreen: button announces "Expand chart to fullscreen" / "Exit chart fullscreen"; focus retained. Automated / source / docs (re-confirmed on current main): - [x] a11y Vitest green — 9 files / 88 tests (PriceChart incl. interval+attribution, OrderBookPanel, WalletButton, PriceChartLightweightCanvas). - [x] @axe-core/playwright (^4.11.3) + e2e/a11y-critical-routes.spec.ts in e2e-smoke (your agent's axe run was 4/4; trade/charts/wallet, WCAG 2.1 A/AA critical+serious gate). - [x] aria contracts present in source: chart role=region + aria-labelledby + aria-live; order book table + th scope=col; tab/tabpanel; wallet focus-in/return; the agent-QA axe fixes (attributionLogo:false, TradingView attribution link, scrollable-region tabindex). - [x] docs/frontend.md § Accessibility CI + skills/AGENTS_FRONTEND_A11Y_CI.md; gap row Accessibility -> Improved, backlog a11y CI -> Done. Remaining: the UI sound-effects sub-checklist is @totdking's (audio feedback, not assistive-tech) — separate from the a11y/AT criteria above. So all the acceptance + verification criteria in the a11y/AT scope are met. Good to close once @totdking signs off the sounds sub-checklist. @PlasticDigits
Brouie commented 2026-06-02 17:53:07 +00:00 (Migrated from gitlab.com)

Follow-up to my AT note above — I also ran the sound-effects sub-checklist, all pass:

  • Connect wallet -> option click plays press sound
  • Trade chart interval buttons -> press sound
  • Chart fullscreen expand/exit -> press sound
  • Theme toggle -> press sound
  • Wallet menu open (chip) -> press sound

So nothing is outstanding now: AT (NVDA) 5/5 + sounds 5/5 + the automated/axe/docs layer all verified. Fully good to close. @PlasticDigits

Follow-up to my AT note above — I also ran the sound-effects sub-checklist, all pass: - [x] Connect wallet -> option click plays press sound - [x] Trade chart interval buttons -> press sound - [x] Chart fullscreen expand/exit -> press sound - [x] Theme toggle -> press sound - [x] Wallet menu open (chip) -> press sound So nothing is outstanding now: AT (NVDA) 5/5 + sounds 5/5 + the automated/axe/docs layer all verified. Fully good to close. @PlasticDigits
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-03 02:05:25 +00:00
PlasticDigits commented 2026-06-12 05:05:53 +00:00 (Migrated from gitlab.com)

mentioned in issue #366

mentioned in issue #366
PlasticDigits commented 2026-06-12 05:06:04 +00:00 (Migrated from gitlab.com)

mentioned in issue #361

mentioned in issue #361
PlasticDigits commented 2026-06-26 06:50:32 +00:00 (Migrated from gitlab.com)

mentioned in issue #422

mentioned in issue #422
PlasticDigits commented 2026-08-15 12:19:49 +00:00 (Migrated from gitlab.com)

mentioned in issue #524

mentioned in issue #524
PlasticDigits commented 2026-08-26 04:22:42 +00:00 (Migrated from gitlab.com)

mentioned in issue #671

mentioned in issue #671
PlasticDigits commented 2026-08-29 14:16:58 +00:00 (Migrated from gitlab.com)

mentioned in issue #705

mentioned in issue #705
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#214
No description provided.