fix: connected wallet dropdown alignment and spacing #671

Closed
opened 2026-08-26 04:22:40 +00:00 by PlasticDigits · 12 comments
PlasticDigits commented 2026-08-26 04:22:40 +00:00 (Migrated from gitlab.com)

Summary

Fix alignment and spacing on the connected wallet dropdown (header chip menu — the surface QA called the “wallet modal”). Bundle menu-row icon+label layout + header address wrap + docs/verify in one issue. Do not split “CSS flex” vs “AddressRow wrap” vs “Copy address vs other rows.”

Related: #185 (labeled menu rows), #140 (chip + dropdown), #183 (CopyButton), #188 (AddressRow), #186 (chip network), #187 (dismiss + Escape), #144 (:focus-visible on .wallet-menu-item), #214 (focus into first menuitem), #488 / #489 (tokens + no lectures).

Not this issue: WalletModal.tsx (disconnected Connect Wallet dialog — wallet-option-card). The broken surface is .wallet-menu after the connected chip is opened.

Current codebase

The connected header chip opens an absolute .wallet-menu under .wallet-dropdown-wrap. Header = LUNC balance + full-bech32 AddressRow. Body = labeled rows from #185 plus Portfolio / Trader / Disconnect. Copy address is the only row that lays out icon+label horizontally. Every other row stacks the icon above the label. The header address wraps mid-string with an underline and leaves copy/explorer icons on a following line.

Layer Behavior today
Menu shell WalletButton.tsx: showDropdown → .wallet-menu + app-menu-dismiss + role="menu". Header WalletLuncBalance + AddressRow showFull (data-testid="wallet-menu-address-row"). Then WalletDropdownMenuItems + Link My Portfolio + Link Trader profile + Disconnect.
Labeled rows (#185) WalletDropdownMenuItems.tsx: CopyButton menuLabel="Copy address"; explorer <a class="wallet-menu-item">; switch <button class="wallet-menu-item">. Testids wallet-menu-copy-address / wallet-menu-view-explorer / wallet-menu-switch-wallet.
Why Copy address looks OK CopyButton.tsx menuLabel branch adds Tailwind inline-flex items-center gap-2 on top of wallet-menu-item. Icon-only / buttonLabel variants are other surfaces.
Why other rows stack index.css ~1152–1178: .app-menu-link is in the shared nav group (display: inline-flex; align-items: center; gap: 8px). .wallet-menu-item is not. The later shared rule (~1277–1293) only sets justify-content: flex-start, padding, width, and focus — no display / align-items / gap / flex-wrap: nowrap. Tailwind preflight makes svg { display: block }, so a block SVG + following text wraps to two lines.
Header address wrap AddressRow.tsx: showFull → full bech32 + break-all + underline on the label <a>, parent inline-flex flex-wrap. Menu min-width is 210px (.wallet-menu). Icons wrap onto their own line. Chip trigger already uses shortenAddress.
Docs / playbook docs/frontend.md § Connected wallet dropdown: order + testids, no row-layout invariant. § AddressRow: showFull + break-all is documented as intended for this menu. skills/AGENTS_FRONTEND_WALLET_CHIP.md lists affordances, not flex alignment.
Tests today WalletButton.test.tsx, WalletDropdownMenuItems.test.tsx, CopyButton.test.tsx, AddressRow.test.tsx, e2e/navigation.spec.ts wallet block: presence / click / focus, never computed flex or icon/label bounding boxes.

Why this is needed

  1. The menu looks broken. Retailers open the chip and see five stacked icon-over-label rows and one correct Copy address row. That is a CSS omission, not a missing feature.
  2. #185 shipped labels without a shared row primitive. Only CopyButton opted into flex. Explorer / Switch / Portfolio / Trader / Disconnect did not. Per-row Tailwind will drift again.
  3. Full-bech32 + break-all + underline in a 210px panel wraps mid-string and orphans the header copy/explorer icons. The same actions already exist as labeled rows. The header should stay honest (full address available) without looking like overflow.
  4. Existing tests cannot catch this. They assert text and roles. Layout can regress silently.

Constraints / guardrails

  1. This issue is the connected dropdown only. Do not restyle WalletModal connect cards, WalletConnect pairing, or .app-menu-link unless a shared helper is extracted and those surfaces stay visually unchanged.
  2. Keep #185 / #140 behavior. Menu order: balance + address header → Copy address → View on explorer (omit when URL null) → Switch wallet → My Portfolio → Trader profile → Disconnect. Same data-testids. Switch still disconnect() then setWalletModalOpen(true). Copy still writes the full bech32 via copyToClipboard / CopyButton. Explorer still getExplorerAddressUrl only (noopener noreferrer).
  3. Fix layout in .wallet-menu-item, not by copying Tailwind onto every row. Shared rule must include display: inline-flex (or flex), align-items: center, gap (8px to match .app-menu-link), flex-wrap: nowrap, justify-content: flex-start. CopyButton menuLabel may drop redundant Tailwind once the class is correct.
  4. Do not break other CopyButton modes. Icon-only (AddressRow / TokenIdentity) and buttonLabel (WalletConnect #519) must stay icon-button / pairing-copy. Do not put role="menuitem" on those.
  5. Address header: compact, not a lecture (#489). Prefer single-line truncated label (terra1…suffix) with title / accessible name = full bech32, or a wrap that does not mid-word-underline a 44-char string and does not drop icons onto a lonely second row. Full address must remain copyable. Do not add “this is your wallet address” copy. Changing showFull for this consumer is allowed; default AddressRow (Pool / Trader / Protocol hub) must still support showFull + shorten for other pages.
  6. Header icons vs labeled rows. Duplicate copy/explorer in the header is OK if they stay one horizontal cluster. Do not remove labeled #185 rows to “clean up” the header.
  7. Tokens / chrome (#488 / #653). Use existing --ink* / --menu-bg / --line. No *-neo. No extra card-glass / shell-panel inside the menu. Light + dark.
  8. a11y (#144 / #214 / #187). Keep :focus-visible on .wallet-menu-item / .copy-button. First menuitem still receives focus on open; Escape + dismiss still close and return focus to the chip. Hit targets stay usable (≥ existing padding). Do not shrink icons below w-4 h-4.
  9. Viewport. Desktop (sm+) and phone (<sm chip). Menu already has max-height + overflow-y: auto (short / mobile). Rows must not force a 2× height from stacked icons. Menu must not cover the mobile tab bar more than it does today (z-index 55).
  10. No wallet / chain logic. No new LCD, clipboard polyfill, explorer host, or mnemonic/UI secrets. Address is already public bech32.
  11. Docs. Add a row-layout invariant to docs/frontend.md § Connected wallet dropdown and AGENTS_FRONTEND_WALLET_CHIP.md. Update AddressRow notes if the wallet header no longer uses showFull wrap.

Relevant files

File Role
frontend-dapp/src/index.css .wallet-menu, .wallet-menu-item (primary fix)
frontend-dapp/src/components/wallet/WalletButton.tsx Menu shell + Portfolio / Trader / Disconnect rows
frontend-dapp/src/components/wallet/WalletDropdownMenuItems.tsx Copy / explorer / switch rows
frontend-dapp/src/components/ui/CopyButton.tsx menuLabel row; drop redundant flex if CSS owns it
frontend-dapp/src/components/ui/AddressRow.tsx Header wrap / truncate (wallet consumer and/or optional prop)
frontend-dapp/src/components/wallet/WalletLuncBalance.tsx Header balance (spacing only if needed)
frontend-dapp/src/components/wallet/__tests__/WalletButton.test.tsx Open menu; add layout assertions
frontend-dapp/src/components/wallet/__tests__/WalletDropdownMenuItems.test.tsx Row presence + layout
frontend-dapp/src/components/ui/__tests__/CopyButton.test.tsx menuLabel still menuitem; icon-only unchanged
frontend-dapp/src/components/ui/__tests__/AddressRow.test.tsx Wallet-header display mode
frontend-dapp/e2e/navigation.spec.ts Connected dropdown; add geometry checks
frontend-dapp/e2e/helpers/wallet-ui.ts Chip locators
docs/frontend.md Dropdown + AddressRow invariants
skills/AGENTS_FRONTEND_WALLET_CHIP.md Layout rule
skills/AGENTS_FRONTEND_ADDRESS_ROW.md If header display mode changes
scripts/qa/verify-issue-NNN.sh + Makefile New make verify-issue-NNN
  1. Add the missing flex row rules on .wallet-menu-item next to the existing override (same gap/alignment as .app-menu-link). Keep the flat :focus-visible ring.
  2. Optionally add .wallet-menu-item to the shared nav inline-flex group only if that does not re-apply chrome borders / min-height: 36px / centered justify that the override already strips. Safer: set display/align/gap on .wallet-menu-item itself.
  3. Header: for wallet-menu-address-row, use truncated shortenAddress (match chip density, e.g. 8/6 or 6/6) + title={address} + keep icon cluster flex-nowrap on the same row. Clipboard / explorer still use the full string. If showFull must remain for QA, constrain wrap (overflow-wrap + no underline on the wrapping label; icons shrink-0 on the first line).
  4. Add Vitest toHaveStyle / getComputedStyle (or jsdom class assertions) that every [role=menuitem] in the open menu is a horizontal flex row. Playwright: icon and label bounding boxes share a row (icon y ≈ label y, icon x < label x) on desktop and 390px.
  5. make verify-issue-NNN: WalletButton + dropdown + CopyButton menuLabel + AddressRow + CSS grep for .wallet-menu-item flex + docs/skill invariants. Optional Playwright when LocalTerra is up (5 workers; no e2e-tx).

Acceptance criteria

  • Every connected-menu row (Copy address, View on explorer, Switch wallet, My Portfolio, Trader profile, Disconnect) is a single horizontal row: icon left, label vertically centered, consistent gap.
  • Vertical rhythm between rows is even (shared padding). No extra-tall Copy address vs stacked neighbors.
  • Header address does not mid-word-wrap an underlined 44-char bech32 with orphaned copy/explorer icons.
  • Full bech32 still copies; explorer URL still from getExplorerAddressUrl; row hidden when URL is null.
  • Menu order, testids, Switch wallet → connect modal, dismiss / Escape / first-item focus unchanged.
  • Icon-only CopyButton and buttonLabel pairing copy unchanged.
  • Light + dark; desktop + phone chip; menu still scrollable on short viewports.
  • docs/frontend.md + AGENTS_FRONTEND_WALLET_CHIP.md document the row-layout invariant. make verify-issue-NNN exists and is green.

Test plan (all paths)

CSS / unit

  • Open connected menu: each [role=menuitem] computed display is flex or inline-flex; flex-direction is row; align-items is center; flex-wrap is nowrap (or equivalent class contract).
  • Copy address still role="menuitem" and copies the full address (existing CopyButton test).
  • Explorer menuitem href = helper URL; omitted when helper returns null.
  • Switch wallet still calls disconnect then setWalletModalOpen(true).
  • Portfolio / Trader hrefs unchanged (/portfolio, /trader/${address}).
  • Disconnect still calls disconnect and closes the menu.
  • Address header: visible label is truncated or wraps without orphaned icons; title or accessible name includes full bech32; copy still writes full string.
  • AddressRow without the wallet-header mode (Pool LP, trader header, Protocol hub) still shortens by default and still supports showFull where those pages need it.
  • Icon-only CopyButton: no role="menuitem", still aria-label only.
  • buttonLabel pairing copy: not a menuitem (existing #519 test).
  • Focus: first menuitem focused on open; Escape returns to chip; dismiss button present (#187 / #214).
  • Theme: no hardcoded # palette on these rows; no *-neo.

E2E (Playwright, 5 workers, no e2e-tx)

  • Connect simulated wallet → open chip → all six rows visible.
  • For each row: icon box and label box share a horizontal band (centers within ~4px vertically; icon left of text).
  • Phone 390 and desktop 1280: same row geometry; menu does not overflow the viewport horizontally.
  • Copy address still copies; View on explorer has target="_blank" + rel including noopener; Switch wallet opens Connect Wallet heading.
  • Dark and light (data-theme) smoke: menu readable, no clipped labels.

Docs / verify

  • docs/frontend.md states icon+label are one row via .wallet-menu-item.
  • Skill AGENTS_FRONTEND_WALLET_CHIP.md forbids stacking icons above labels.
  • make verify-issue-NNN greps the flex rule + runs the unit files above.

Test plan (attack, hack, and abuse)

  • Clipboard injection. Menu copy writes only the connected address string (trimmed by existing helper). Do not copy innerHTML, a javascript: URL, or attacker-controlled query text. Existing empty/whitespace copyToClipboard fail path unchanged.
  • Open redirect / explorer. href only from getExplorerAddressUrl. If helper is mocked to javascript:alert(1) or data:text/html,…, treat as invalid / omit the row (same as null) — do not render a live javascript: link. Keep rel="noopener noreferrer".
  • XSS in address display. Bech32 is a text node / React children. <script>, "><img src=x onerror=…>, HTML entities in a spoofed address string must not execute. title must not break out of the attribute.
  • Router injection. Portfolio / Trader links stay app routes (/portfolio, /trader/${address}). Do not accept address that turns the path into //evil or https://…. Invalid / empty address: do not render the connected menu (existing gate is if (address)).
  • Clickjacking / overlay. Do not raise .wallet-menu z-index above modal portals (connect modal / terms). Dismiss backdrop still covers the page so a leftover menu cannot sit over Swap CTAs after “close.”
  • Focus / keyboard trap. Escape and dismiss still work. Do not preventDefault on Tab in a way that traps the user. :focus-visible ring remains visible (no outline: none without a replacement ring).
  • Hit-target spoof. Icon and label stay one control (role="menuitem"). Do not nest a second <a> inside a menuitem (double navigation / overlay). Header AddressRow links stay separate from menu rows.
  • Sensitive data. Do not render mnemonic, private key, WalletConnect pairing secret, or LCD auth in this menu. Balance line stays public bank uluna only.
  • DoS / layout. No per-row timers, no N+1 LCD on open (balance already uses shared useNativeUlunaBalance). Menu max-height + overflow stays so a zoomed/short viewport cannot lock the page scroll.
  • Theme / contrast. Light theme must not inherit an unreadable color-scheme button face (same class of bug as token listboxes). Rows use --ink-dim / --ink on --menu-bg.
  • CopyButton regression. Changing menuLabel classes must not make AddressRow / TokenIdentity / pairing copy into full-width menuitems or steal role="menu" semantics outside the wallet menu.

Verification criteria

Done when:

  1. A reviewer opens the connected header chip (LocalTerra simulated wallet or any connected build) and sees a compact menu: header balance + tidy address cluster; every action is icon-left + label-right on one line; spacing matches between rows.
  2. Copy / explorer / switch / portfolio / trader / disconnect still do exactly what #185 / #140 specified.
  3. make test-frontend covers the new layout assertions; make verify-issue-NNN is green; Playwright geometry checks pass when the smoke stack is up.
  4. Docs no longer imply that showFull + break-all mid-wrap is the desired wallet-header look (unless a reviewer explicitly keeps full wrap and it no longer orphans icons).
  5. Abuse cases above are unit-covered (href omit, text-node address, CopyButton modes) or explicitly unchanged (clipboard helper, explorer allowlist).
make test-frontend
make lint-frontend
make verify-issue-NNN
# optional, LocalTerra up:
#   sg docker -c 'CI=1 make test-e2e'   # smoke only; do not require e2e-tx
## Summary Fix **alignment and spacing** on the **connected wallet dropdown** (header chip menu — the surface QA called the “wallet modal”). Bundle **menu-row icon+label layout** + **header address wrap** + **docs/verify** in **one** issue. Do **not** split “CSS flex” vs “AddressRow wrap” vs “Copy address vs other rows.” Related: [#185](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/185) (labeled menu rows), [#140](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/140) (chip + dropdown), [#183](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/183) (`CopyButton`), [#188](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/188) (`AddressRow`), [#186](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/186) (chip network), [#187](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/187) (dismiss + Escape), [#144](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/144) (`:focus-visible` on `.wallet-menu-item`), [#214](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/214) (focus into first menuitem), [#488](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/488) / [#489](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/489) (tokens + no lectures). **Not this issue:** [`WalletModal.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/wallet/WalletModal.tsx) (disconnected **Connect Wallet** dialog — `wallet-option-card`). The broken surface is `.wallet-menu` after the connected chip is opened. ## Current codebase The connected header chip opens an absolute `.wallet-menu` under `.wallet-dropdown-wrap`. Header = LUNC balance + full-bech32 `AddressRow`. Body = labeled rows from `#185` plus Portfolio / Trader / Disconnect. **Copy address** is the only row that lays out icon+label horizontally. Every other row stacks the icon **above** the label. The header address wraps mid-string with an underline and leaves copy/explorer icons on a following line. | Layer | Behavior today | |-------|----------------| | **Menu shell** | [`WalletButton.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/wallet/WalletButton.tsx): `showDropdown` → `.wallet-menu` + `app-menu-dismiss` + `role="menu"`. Header `WalletLuncBalance` + `AddressRow showFull` (`data-testid="wallet-menu-address-row"`). Then `WalletDropdownMenuItems` + `Link` **My Portfolio** + `Link` **Trader profile** + **Disconnect**. | | **Labeled rows (#185)** | [`WalletDropdownMenuItems.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/wallet/WalletDropdownMenuItems.tsx): `CopyButton menuLabel="Copy address"`; explorer `<a class="wallet-menu-item">`; switch `<button class="wallet-menu-item">`. Testids `wallet-menu-copy-address` / `wallet-menu-view-explorer` / `wallet-menu-switch-wallet`. | | **Why Copy address looks OK** | [`CopyButton.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/ui/CopyButton.tsx) `menuLabel` branch adds Tailwind `inline-flex items-center gap-2` **on top of** `wallet-menu-item`. Icon-only / `buttonLabel` variants are other surfaces. | | **Why other rows stack** | [`index.css`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/index.css) ~1152–1178: `.app-menu-link` is in the shared nav group (`display: inline-flex; align-items: center; gap: 8px`). `.wallet-menu-item` is **not**. The later shared rule (~1277–1293) only sets `justify-content: flex-start`, padding, width, and focus — **no** `display` / `align-items` / `gap` / `flex-wrap: nowrap`. Tailwind preflight makes `svg { display: block }`, so a block SVG + following text wraps to two lines. | | **Header address wrap** | [`AddressRow.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/ui/AddressRow.tsx): `showFull` → full bech32 + `break-all` + `underline` on the label `<a>`, parent `inline-flex flex-wrap`. Menu min-width is **210px** (`.wallet-menu`). Icons wrap onto their own line. Chip trigger already uses `shortenAddress`. | | **Docs / playbook** | [`docs/frontend.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/frontend.md) § Connected wallet dropdown: order + testids, **no** row-layout invariant. § AddressRow: `showFull` + `break-all` is **documented as intended** for this menu. [`skills/AGENTS_FRONTEND_WALLET_CHIP.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_FRONTEND_WALLET_CHIP.md) lists affordances, not flex alignment. | | **Tests today** | [`WalletButton.test.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/wallet/__tests__/WalletButton.test.tsx), [`WalletDropdownMenuItems.test.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/wallet/__tests__/WalletDropdownMenuItems.test.tsx), [`CopyButton.test.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/ui/__tests__/CopyButton.test.tsx), [`AddressRow.test.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/ui/__tests__/AddressRow.test.tsx), `e2e/navigation.spec.ts` wallet block: **presence / click / focus**, never computed flex or icon/label bounding boxes. | ## Why this is needed 1. **The menu looks broken.** Retailers open the chip and see five stacked icon-over-label rows and one correct **Copy address** row. That is a CSS omission, not a missing feature. 2. **`#185` shipped labels without a shared row primitive.** Only `CopyButton` opted into flex. Explorer / Switch / Portfolio / Trader / Disconnect did not. Per-row Tailwind will drift again. 3. **Full-bech32 + `break-all` + underline in a 210px panel** wraps mid-string and orphans the header copy/explorer icons. The same actions already exist as labeled rows. The header should stay honest (full address available) without looking like overflow. 4. **Existing tests cannot catch this.** They assert text and roles. Layout can regress silently. ## Constraints / guardrails 1. **This issue is the connected dropdown only.** Do not restyle `WalletModal` connect cards, WalletConnect pairing, or `.app-menu-link` unless a shared helper is extracted and those surfaces stay visually unchanged. 2. **Keep `#185` / `#140` behavior.** Menu order: balance + address header → Copy address → View on explorer (omit when URL `null`) → Switch wallet → My Portfolio → Trader profile → Disconnect. Same `data-testid`s. Switch still `disconnect()` then `setWalletModalOpen(true)`. Copy still writes the **full** bech32 via `copyToClipboard` / `CopyButton`. Explorer still `getExplorerAddressUrl` only (`noopener noreferrer`). 3. **Fix layout in `.wallet-menu-item`, not by copying Tailwind onto every row.** Shared rule must include `display: inline-flex` (or `flex`), `align-items: center`, `gap` (8px to match `.app-menu-link`), `flex-wrap: nowrap`, `justify-content: flex-start`. `CopyButton` `menuLabel` may drop redundant Tailwind once the class is correct. 4. **Do not break other `CopyButton` modes.** Icon-only (AddressRow / TokenIdentity) and `buttonLabel` (WalletConnect #519) must stay icon-button / pairing-copy. Do not put `role="menuitem"` on those. 5. **Address header: compact, not a lecture (#489).** Prefer single-line truncated label (`terra1…suffix`) with `title` / accessible name = full bech32, **or** a wrap that does not mid-word-underline a 44-char string and does not drop icons onto a lonely second row. Full address must remain copyable. Do **not** add “this is your wallet address” copy. Changing `showFull` for **this** consumer is allowed; default `AddressRow` (Pool / Trader / Protocol hub) must still support `showFull` + shorten for other pages. 6. **Header icons vs labeled rows.** Duplicate copy/explorer in the header is OK if they stay one horizontal cluster. Do not remove labeled `#185` rows to “clean up” the header. 7. **Tokens / chrome (#488 / #653).** Use existing `--ink*` / `--menu-bg` / `--line`. No `*-neo`. No extra `card-glass` / `shell-panel` inside the menu. Light + dark. 8. **a11y (#144 / #214 / #187).** Keep `:focus-visible` on `.wallet-menu-item` / `.copy-button`. First menuitem still receives focus on open; Escape + dismiss still close and return focus to the chip. Hit targets stay usable (≥ existing padding). Do not shrink icons below `w-4 h-4`. 9. **Viewport.** Desktop (`sm+`) and phone (`<sm` chip). Menu already has `max-height` + `overflow-y: auto` (short / mobile). Rows must not force a 2× height from stacked icons. Menu must not cover the mobile tab bar more than it does today (`z-index` 55). 10. **No wallet / chain logic.** No new LCD, clipboard polyfill, explorer host, or mnemonic/UI secrets. Address is already public bech32. 11. **Docs.** Add a row-layout invariant to `docs/frontend.md` § Connected wallet dropdown and `AGENTS_FRONTEND_WALLET_CHIP.md`. Update AddressRow notes if the wallet header no longer uses `showFull` wrap. ## Relevant files | File | Role | |------|------| | `frontend-dapp/src/index.css` | `.wallet-menu`, `.wallet-menu-item` (primary fix) | | `frontend-dapp/src/components/wallet/WalletButton.tsx` | Menu shell + Portfolio / Trader / Disconnect rows | | `frontend-dapp/src/components/wallet/WalletDropdownMenuItems.tsx` | Copy / explorer / switch rows | | `frontend-dapp/src/components/ui/CopyButton.tsx` | `menuLabel` row; drop redundant flex if CSS owns it | | `frontend-dapp/src/components/ui/AddressRow.tsx` | Header wrap / truncate (wallet consumer and/or optional prop) | | `frontend-dapp/src/components/wallet/WalletLuncBalance.tsx` | Header balance (spacing only if needed) | | `frontend-dapp/src/components/wallet/__tests__/WalletButton.test.tsx` | Open menu; add layout assertions | | `frontend-dapp/src/components/wallet/__tests__/WalletDropdownMenuItems.test.tsx` | Row presence + layout | | `frontend-dapp/src/components/ui/__tests__/CopyButton.test.tsx` | `menuLabel` still menuitem; icon-only unchanged | | `frontend-dapp/src/components/ui/__tests__/AddressRow.test.tsx` | Wallet-header display mode | | `frontend-dapp/e2e/navigation.spec.ts` | Connected dropdown; add geometry checks | | `frontend-dapp/e2e/helpers/wallet-ui.ts` | Chip locators | | `docs/frontend.md` | Dropdown + AddressRow invariants | | `skills/AGENTS_FRONTEND_WALLET_CHIP.md` | Layout rule | | `skills/AGENTS_FRONTEND_ADDRESS_ROW.md` | If header display mode changes | | `scripts/qa/verify-issue-NNN.sh` + `Makefile` | New `make verify-issue-NNN` | ## Recommended direction 1. Add the missing flex row rules on `.wallet-menu-item` next to the existing override (same gap/alignment as `.app-menu-link`). Keep the flat `:focus-visible` ring. 2. Optionally add `.wallet-menu-item` to the shared nav `inline-flex` group **only if** that does not re-apply chrome borders / `min-height: 36px` / centered justify that the override already strips. Safer: set display/align/gap on `.wallet-menu-item` itself. 3. Header: for `wallet-menu-address-row`, use truncated `shortenAddress` (match chip density, e.g. 8/6 or 6/6) + `title={address}` + keep icon cluster `flex-nowrap` on the same row. Clipboard / explorer still use the full string. If `showFull` must remain for QA, constrain wrap (`overflow-wrap` + no underline on the wrapping label; icons `shrink-0` on the first line). 4. Add Vitest `toHaveStyle` / `getComputedStyle` (or jsdom class assertions) that every `[role=menuitem]` in the open menu is a horizontal flex row. Playwright: icon and label bounding boxes share a row (icon `y` ≈ label `y`, icon `x` < label `x`) on desktop and 390px. 5. `make verify-issue-NNN`: WalletButton + dropdown + CopyButton menuLabel + AddressRow + CSS grep for `.wallet-menu-item` flex + docs/skill invariants. Optional Playwright when LocalTerra is up (5 workers; **no** `e2e-tx`). ## Acceptance criteria - [ ] Every connected-menu row (Copy address, View on explorer, Switch wallet, My Portfolio, Trader profile, Disconnect) is a **single horizontal row**: icon left, label vertically centered, consistent gap. - [ ] Vertical rhythm between rows is even (shared padding). No extra-tall Copy address vs stacked neighbors. - [ ] Header address does not mid-word-wrap an underlined 44-char bech32 with orphaned copy/explorer icons. - [ ] Full bech32 still copies; explorer URL still from `getExplorerAddressUrl`; row hidden when URL is `null`. - [ ] Menu order, testids, Switch wallet → connect modal, dismiss / Escape / first-item focus unchanged. - [ ] Icon-only `CopyButton` and `buttonLabel` pairing copy unchanged. - [ ] Light + dark; desktop + phone chip; menu still scrollable on short viewports. - [ ] `docs/frontend.md` + `AGENTS_FRONTEND_WALLET_CHIP.md` document the row-layout invariant. `make verify-issue-NNN` exists and is green. ## Test plan (all paths) **CSS / unit** - [ ] Open connected menu: each `[role=menuitem]` computed `display` is `flex` or `inline-flex`; `flex-direction` is `row`; `align-items` is `center`; `flex-wrap` is `nowrap` (or equivalent class contract). - [ ] Copy address still `role="menuitem"` and copies the full address (existing CopyButton test). - [ ] Explorer menuitem `href` = helper URL; omitted when helper returns `null`. - [ ] Switch wallet still calls `disconnect` then `setWalletModalOpen(true)`. - [ ] Portfolio / Trader `href`s unchanged (`/portfolio`, `/trader/${address}`). - [ ] Disconnect still calls `disconnect` and closes the menu. - [ ] Address header: visible label is truncated **or** wraps without orphaned icons; `title` or accessible name includes full bech32; copy still writes full string. - [ ] `AddressRow` without the wallet-header mode (Pool LP, trader header, Protocol hub) still shortens by default and still supports `showFull` where those pages need it. - [ ] Icon-only CopyButton: no `role="menuitem"`, still `aria-label` only. - [ ] `buttonLabel` pairing copy: not a menuitem (existing #519 test). - [ ] Focus: first menuitem focused on open; Escape returns to chip; dismiss button present (#187 / #214). - [ ] Theme: no hardcoded `#` palette on these rows; no `*-neo`. **E2E (Playwright, 5 workers, no `e2e-tx`)** - [ ] Connect simulated wallet → open chip → all six rows visible. - [ ] For each row: icon box and label box share a horizontal band (centers within ~4px vertically; icon left of text). - [ ] Phone 390 and desktop 1280: same row geometry; menu does not overflow the viewport horizontally. - [ ] Copy address still copies; View on explorer has `target="_blank"` + `rel` including `noopener`; Switch wallet opens Connect Wallet heading. - [ ] Dark and light (`data-theme`) smoke: menu readable, no clipped labels. **Docs / verify** - [ ] `docs/frontend.md` states icon+label are one row via `.wallet-menu-item`. - [ ] Skill `AGENTS_FRONTEND_WALLET_CHIP.md` forbids stacking icons above labels. - [ ] `make verify-issue-NNN` greps the flex rule + runs the unit files above. ## Test plan (attack, hack, and abuse) - [ ] **Clipboard injection.** Menu copy writes only the connected `address` string (trimmed by existing helper). Do not copy innerHTML, a `javascript:` URL, or attacker-controlled query text. Existing empty/whitespace `copyToClipboard` fail path unchanged. - [ ] **Open redirect / explorer.** `href` only from `getExplorerAddressUrl`. If helper is mocked to `javascript:alert(1)` or `data:text/html,…`, treat as invalid / omit the row (same as `null`) — do not render a live `javascript:` link. Keep `rel="noopener noreferrer"`. - [ ] **XSS in address display.** Bech32 is a text node / React children. `<script>`, `"><img src=x onerror=…>`, HTML entities in a spoofed address string must not execute. `title` must not break out of the attribute. - [ ] **Router injection.** Portfolio / Trader links stay app routes (`/portfolio`, `/trader/${address}`). Do not accept `address` that turns the path into `//evil` or `https://…`. Invalid / empty address: do not render the connected menu (existing gate is `if (address)`). - [ ] **Clickjacking / overlay.** Do not raise `.wallet-menu` `z-index` above modal portals (connect modal / terms). Dismiss backdrop still covers the page so a leftover menu cannot sit over Swap CTAs after “close.” - [ ] **Focus / keyboard trap.** Escape and dismiss still work. Do not `preventDefault` on Tab in a way that traps the user. `:focus-visible` ring remains visible (no `outline: none` without a replacement ring). - [ ] **Hit-target spoof.** Icon and label stay one control (`role="menuitem"`). Do not nest a second `<a>` inside a menuitem (double navigation / overlay). Header AddressRow links stay separate from menu rows. - [ ] **Sensitive data.** Do not render mnemonic, private key, WalletConnect pairing secret, or LCD auth in this menu. Balance line stays public bank `uluna` only. - [ ] **DoS / layout.** No per-row timers, no N+1 LCD on open (balance already uses shared `useNativeUlunaBalance`). Menu `max-height` + overflow stays so a zoomed/short viewport cannot lock the page scroll. - [ ] **Theme / contrast.** Light theme must not inherit an unreadable `color-scheme` button face (same class of bug as token listboxes). Rows use `--ink-dim` / `--ink` on `--menu-bg`. - [ ] **CopyButton regression.** Changing `menuLabel` classes must not make AddressRow / TokenIdentity / pairing copy into full-width menuitems or steal `role="menu"` semantics outside the wallet menu. ## Verification criteria Done when: 1. A reviewer opens the **connected** header chip (LocalTerra simulated wallet or any connected build) and sees a compact menu: header balance + tidy address cluster; every action is icon-left + label-right on one line; spacing matches between rows. 2. Copy / explorer / switch / portfolio / trader / disconnect still do exactly what `#185` / `#140` specified. 3. `make test-frontend` covers the new layout assertions; `make verify-issue-NNN` is green; Playwright geometry checks pass when the smoke stack is up. 4. Docs no longer imply that `showFull` + `break-all` mid-wrap is the desired wallet-header look (unless a reviewer explicitly keeps full wrap **and** it no longer orphans icons). 5. Abuse cases above are unit-covered (href omit, text-node address, CopyButton modes) or explicitly unchanged (clipboard helper, explorer allowlist). ```bash make test-frontend make lint-frontend make verify-issue-NNN # optional, LocalTerra up: # sg docker -c 'CI=1 make test-e2e' # smoke only; do not require e2e-tx ```
PlasticDigits commented 2026-08-26 04:22:42 +00:00 (Migrated from gitlab.com)

marked as related to #185

marked as related to #185
PlasticDigits commented 2026-08-26 04:22:43 +00:00 (Migrated from gitlab.com)

marked as related to #140

marked as related to #140
PlasticDigits commented 2026-08-26 04:22:44 +00:00 (Migrated from gitlab.com)

marked as related to #188

marked as related to #188
PlasticDigits commented 2026-08-26 04:22:44 +00:00 (Migrated from gitlab.com)

marked as related to #183

marked as related to #183
PlasticDigits commented 2026-08-26 04:22:45 +00:00 (Migrated from gitlab.com)

marked as related to #144

marked as related to #144
PlasticDigits commented 2026-08-26 07:16:51 +00:00 (Migrated from gitlab.com)

mentioned in commit 756acf1edc

mentioned in commit 756acf1edc4ccb95b5e8854bdc6c08cc4729e88a
PlasticDigits commented 2026-08-26 07:16:52 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1169

mentioned in merge request !1169
PlasticDigits commented 2026-08-26 08:34:16 +00:00 (Migrated from gitlab.com)

mentioned in commit c7b888ff56

mentioned in commit c7b888ff56cc8d6f63668a24307fd8db060a9292
PlasticDigits commented 2026-08-26 08:34:25 +00:00 (Migrated from gitlab.com)

mentioned in commit fa67862b24

mentioned in commit fa67862b24705fb8ab552ba33e6b910d2e1a2624
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-26 08:34:26 +00:00
PlasticDigits commented 2026-08-26 09:30:54 +00:00 (Migrated from gitlab.com)

mentioned in issue #672

mentioned in issue #672
PlasticDigits commented 2026-08-26 09:31:02 +00:00 (Migrated from gitlab.com)

Merged to main via !1169. Close tests from #672 were kept in navigation.spec.ts.

Leftover: connected wallet dropdown icon+label rows after frontend rebuild. make verify-issue-671.

Merged to `main` via !1169. Close tests from #672 were kept in `navigation.spec.ts`. Leftover: connected wallet dropdown icon+label rows after frontend rebuild. `make verify-issue-671`.
PlasticDigits commented 2026-08-26 09:31:35 +00:00 (Migrated from gitlab.com)

mentioned in issue #673

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