Merge pull request #296 from MeshJS/claude/ballot-vote-ux
feat(governance): clearer ballot voting UX (segmented vote + one entry per action)
feat(governance): clearer ballot voting UX (segmented vote + one entry per action)
# Conflicts: # src/components/pages/wallet/governance/proposals.tsx
# Conflicts: # src/components/pages/wallet/governance/proposals.tsx
fix(assets): stop token rows overflowing the Assets card
fix(ux): themed, non-overflowing, mobile-friendly pagination
The proposal meta row showed the governance action type as plain uppercase
text ("TREASURY WITHDRAWALS"), visually indistinguishable from the other meta
fields and hard to scan in a long list.
Add a GovernanceTypeChip that renders each Conway action type as a color-coded
outline Badge with an icon:
- treasury_withdrawals amber Coins
- info_action blue FileText
- parameter_change purple Settings2
- hard_fork_initiation orange GitBranch
- no_confidence red XCircle
- new_constitution teal FileText
- new/update_committee indigo Users
- unknown slate Hash (Title-Cased fallback label)
Used in both the mobile and desktop meta rows (single shared component, no
view drift). Purely presentational — no data or vote-flow changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The per-proposal voting controls were confusing: a raw OS <select> for the
Yes/No/Abstain choice, plus TWO competing actions (a green "Add to Ballot"
button in proposals.tsx AND a mystery ballot icon in VoteButton).
- Replace the native <select> with a shadcn segmented Yes/No/Abstain control
(color-coded selected state, icons). Guard `(v) => v && setVoteKind(v)` so a
re-click can't blank the vote read into the tx.
- One clear primary: the Vote button now states the choice ("Vote Yes",
"Vote Yes (Proxy)") and uses the themed primary style.
- One ballot entry: the icon-only ballot button becomes a labeled "Add to
ballot" / "In N ballots" secondary, with a tooltip distinguishing the two
flows (vote on-chain now vs collect for co-signers).
- Remove the now-duplicate green "Add to Ballot" buttons (mobile + desktop) in
proposals.tsx; keep the View links.
vote()/voteProxy() bodies, proxy path, keepRelevant, metadata label 674, the
closed-vote Lock state, and all toasts are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Long token names ($drep.collective) and raw quantities pushed the value/ticker past the card's right edge (the clipped "↗1 $S…"). Root cause: the flex row + left group had no min-w-0, so text couldn't shrink, and the name/quantity/ticker were never truncated. - min-w-0 on the row + flex-1 min-w-0 on the left group (lets text shrink). - 60px avatar wrappers flex-shrink-0 (so the image never compresses instead). - name h3 truncates (+ title tooltip) and is bounded via truncateTokenSymbol for the raw-unit hex fallback; remove ml-auto from the link. - value block flex-shrink-0; quantity via numberWithCommas (tabular-nums); ticker truncates with a max width + title. Full name stays reachable via tooltip + the Cardanoscan token link. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The on-chain history rows (desktop table + mobile cards) led with the raw
truncated tx hash — the least human-meaningful field — while the actual
description ("Ballot Vote: …") was buried below. Flip the hierarchy to match
the pending-tx cards:
- primary: the dbTransaction description, or a "Sent"/"Received" fallback (and
the cert label on desktop) so rows without a DB record aren't identified by
a bare hash.
- date below.
- hash demoted to a quiet muted mono link with the external-link arrow.
Also standardize the hash truncation on getFirstAndLast(hash, 8, 8) (was two
different inline substring schemes) and drop the now-redundant standalone
description block on mobile. Cardanoscan links, outputs, certs, signers, and
the row actions menu are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
feat(ux): friendly-error helper + toastError wrapper
fix(mobile): keep dialogs within the viewport (responsive width + dvh + scroll)
PR 5 of the UX/mobile quick-wins pass.
- src/utils/errors.ts `getFriendlyError(error)` maps the common raw errors
(CIP-30 {code:-2}, account-changed, 429/too-many-requests, insufficient
funds, Blockfrost/UTXOS, user-decline) to short human messages, falling back
to the raw message.
- src/utils/toast-error.ts `toastError(error, title?)` — destructive toast with
the normalized message (additive; doesn't touch the TOAST_LIMIT=1 reducer).
- Adopt in DRep registration's catch (raw e.message -> getFriendlyError), while
keeping the "Copy Error" action for raw debug details.
Other error-prone flows (new-transaction, WalletAuthModal) can adopt the
helper incrementally.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
PR 4 of the UX/mobile quick-wins pass. Centered Dialogs overflowed small screens — content (and action buttons) got clipped off-screen, especially the wide governance modals. Make the base DialogContent mobile-safe without changing desktop layout: w-[calc(100%-1.5rem)] (stay within the viewport with a small margin), max-h-[90dvh] + overflow-y-auto (scroll internally instead of off-screen). The max-w-lg cap and centered position are unchanged at desktop. BallotModal and RegisterDrepModal switch their max-h from vh to dvh so the mobile toolbar doesn't hide the bottom. (Kept dialogs centered rather than converting to a bottom sheet to avoid restructuring positioning on the shared base that backs every dialog; the overflow fix is the critical part. Needs a quick on-device / Chrome-MCP visual check before promoting past preprod.) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
fix(mobile): bech32-safe address inputs + decimal keypad + no iOS zoom
feat(ux): Skeleton + EmptyState primitives; fix blank pages & loading/empty bug
feat(mobile): >=44px touch targets on coarse-pointer devices
feat(mobile): viewport-fit + dvh + safe-area insets (foundations)
PR 6 of the UX/mobile quick-wins pass (input correctness).
- Recipient address fields used invalid type="string" with mobile autocorrect/
autocapitalize active — which can silently corrupt case-sensitive bech32
addresses. Now type="text" with inputMode="text" autoCapitalize="off"
autoCorrect="off" spellCheck={false}.
- Amount fields get inputMode="decimal" (numeric keypad on mobile).
- Base Input uses text-base on mobile (sm:text-sm on desktop) so iOS Safari
no longer zooms in on focus (<16px triggers it).
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
PR 3 of the UX/mobile quick-wins pass. - Add shared `Skeleton` (shadcn) and `EmptyState` (Card-based) primitives. - Wallet detail routes (info/transactions/governance/assets) rendered a blank fragment while `appWallet` loaded — replace with a `WalletDetailSkeleton` so there's no white flash on every wallet open. - all-transactions: it showed "No transactions yet" *while still loading* (undefined === loading). Split into a skeleton (loading) vs an `EmptyState` (loaded and empty). - proposals: plain-text "No proposals found" → `EmptyState`. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
PR 2 of the UX/mobile quick-wins pass. Buttons/inputs were 32-36px, below the recommended 44px touch target. A single @media (pointer: coarse) rule enlarges interactive controls on touch devices only — no per-call-site edits and zero change to desktop density. Plain text links are intentionally excluded. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Foundational mobile fixes (PR 1 of the UX/mobile quick-wins pass): - viewport meta gains viewport-fit=cover so env(safe-area-inset-*) resolves (without it the insets are always 0). - Full-height containers use 100dvh instead of 100vh/h-screen so the layout isn't clipped by mobile-Safari / wallet-webview dynamic toolbars (_app, layout root + inner content column). - Main header grows by the safe-area top inset on mobile so it clears the notch/status bar, and honors side insets in landscape. - Mobile nav drawer offsets by the safe-area top, uses 100dvh, and pads the bottom for the home indicator. - Bottom Sheet variant pads the bottom safe area. Desktop is unchanged (insets are 0; header rule is scoped below md). Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
To match new snapshots
fix(governance): verify/merge witnesses with core-cst (DRep-vote signature mismatch)