Skip to content

UX audit: 28 findings against Laws of UX, with 3-sprint fix plan #810

@fmhall

Description

@fmhall

x402scan UX Audit

Audit performed by reading the codebase. Findings are mapped to the Laws of UX and prioritized P0 (ship this week), P1 (ship this month), P2 (when convenient).

Visual nuance (real spacing, contrast, motion timing) wasn't in scope — layout, copy, hierarchy, density, states, and responsive behavior were. See "What I couldn't audit" at the bottom.

TL;DR — the 5 highest-leverage fixes

  1. Pick a top-level navigation model and apply it everywhere. Discover, Composer, and Server detail pages live in three separate layouts with no shared nav. Users get stuck. (Jakob's Law, Mental Model)
  2. Rewrite the hero value prop and add a primary CTA pair above the fold. "The x402 analytics dashboard and block explorer" describes the artifact, not what a user gets. (Aesthetic-Usability, Hick's Law)
  3. Reduce above-the-fold density on Discover. Today: title + tagline + giant search + 4 stat cards + banner + 8-column table + section nav. Compress stats into one row, dismiss the banner by default for repeat visits, hide low-value columns behind a column toggle. (Miller's Law, Choice Overload)
  4. Persist sort, filter, time range, and page in the URL. Today they live in React context only — refresh wipes them and links can't be shared. (Mental Model, Postel's Law)
  5. Fix the "Withdraw Funds" label in the Composer wallet button — for a new user it reads as "remove money," not "add money." (Mental Model, Jakob's Law)

1. Cognition

1.1 Hero copy doesn't sell — apps/scan/src/app/(app)/(home)/(discover)/_components/heading.tsx:18-20 (P0)

"The x402 analytics dashboard and block explorer"

This describes the form, not the value. Visitors don't yet know what x402 is or why they should care.

Fix. Lead with the user's job-to-be-done. Two patterns work:

  • Buyer-side: "Discover and pay any x402 API — no API keys, no signup, just a wallet."
  • Builder-side: "Find every x402-enabled service. See who's earning, who's buying, what works."

Pair the H1 with a subheading that names both audiences in one line: "For agents that pay per call. For developers shipping what they pay for."Occam's Razor: prefer the shorter, sharper form.

1.2 Two competing nav contexts — apps/scan/src/app/(app)/(home)/layout.tsx:11-42 vs apps/scan/src/app/(app)/layout.tsx (P0)

The home tab nav (Discover / All / Marketplace / Transactions / Facilitators / Networks / Ecosystem) only renders inside the (home) group. The Composer, Developer, Server detail, and Buyer/Recipient detail pages have no top-level tabs at all. This forces three different mental models depending on where the user lands. (Mental Model, Jakob's Law — every other explorer / dashboard has one persistent nav.)

Fix. Move the primary tab nav up to (app)/layout.tsx so it persists. Make Composer a tab. Demote routes that don't deserve top-level real estate (Networks, Ecosystem, Facilitators) into a "More" dropdown or sub-nav under "All."

1.3 7 home tabs is over Hick's threshold — (home)/layout.tsx:11-42 (P1)

Discover, All, Marketplace, Transactions, Facilitators, Networks, Ecosystem. Hick's Law says decision time grows non-linearly with options. Worse, three of those (All vs Discover, Marketplace vs Discover, Networks vs Facilitators) overlap conceptually.

Fix. Collapse to 4: Discover · Transactions · Marketplace · More ▾. Move Facilitators, Networks, Ecosystem under "More" — or surface them as filters on Discover instead of as separate destinations. (Choice Overload)

1.4 "Top Sellers" — top by what? — discover/page.tsx:60-67 (P1)

The section title is "Top Sellers" with description "x402Scan curated x402-enabled services." But the table is sorted by an unspecified default and has no visible sort indicators.

Fix. Either rename to match the actual default sort ("Most active sellers — last 24h") or label the active column with a sort caret. Both fixes together is best. (Mental Model)

1.5 "Activity" sparkline column has unclear meaning — discover/_components/columns.tsx:84-97 (P2)

The sparkline sits between the server name and the txn count without an explicit time range or scale. Users don't know if the line is "last 24h" or "all time."

Fix. Tooltip on hover: "Transactions per hour, last 24h". Or kill the column on mobile. (Cognitive Load — visual noise without information transfer.)


2. Perception (Gestalt)

2.1 AgentCash banner breaks content flow — discover/page.tsx:58 (P1)

The banner sits between stats (Overall Stats card row) and table (Top Sellers). Two related blocks of analytics get split by an unrelated promo. (Law of Proximity violation — related elements should sit together.)

Fix. Move the banner above the stats (as a sticky, dismissible bar) or below the fold. After a user dismisses it once it's hidden — but new visitors get the analytics flow uninterrupted on every visit because the dismiss is localStorage-keyed (v2-announcement-banner.tsx:21-29).

2.2 Right-aligned numbers are mis-aligned to "center" — columns.tsx:103-110, 119-126, 132-141 (P1)

Currently text-center font-mono. Numbers should be right-aligned (or decimal-aligned) so digit positions stack — that's how readers compare magnitudes at a glance. Center-alignment defeats the monospace font's purpose.

Fix. Change text-centertext-right (or text-end) on all numeric columns. Headers right-aligned to match. (Law of Similarity — all numeric columns should align consistently with each other.)

2.3 "Try in Poncho" steals attention — columns.tsx:174-202 (P1)

A bright icon-link in the rightmost column on every row that takes users off-platform. (Selective Attention, Von Restorff Effect — anything that breaks pattern in a row gets attention; here it steals it.)

Fix. Move "Try in Poncho" into a row-hover action (only appears on the hovered row), or behind an overflow menu. Better still: make it a button on the server detail page rather than every row.


3. Memory

3.1 Sort and filter state isn't in the URL — _contexts/sorting/* and _contexts/time-range/* (P0)

Sort lives in React context only. Time range and chain do persist (URL + cookie), but page number, sort, and search query don't. Refresh wipes them. Users can't share a link to "sellers sorted by volume in the last 7 days."

Fix. Lift sort + page + search to URL params (use nuqs or a thin wrapper around useSearchParams). Time range already goes there; mirror that pattern for the rest. (Working Memory — don't make users rebuild state every time.)

3.2 Cmd+K search has no recents / no saved searches — _contexts/search/provider.tsx (P1)

The command palette opens fresh every time. No history, no pinned items, no "recently viewed."

Fix. Persist the last 5–10 lookups in localStorage, render them at the top of the empty palette state. Show "Pinned" above that for users who explicitly star an entity. (Serial Position Effect — first/last items are remembered best, so put recent at the top.)


4. Attention

4.1 Wallet button has no visible state — auth-button.tsx:13-30 (P0)

Connected and disconnected look identical (icon-only outline button). New users get no signal that connecting is required for the Composer; existing users get no confirmation they're connected.

Fix. When disconnected: solid primary button labeled "Connect" with wallet icon. When connected: outline button showing 0x12…ab + chain badge + small green dot. Ratio of bytes-to-information here is way off — same pixels, no info.

4.2 Composer onboarding hides the wallet/auth wall (P1)

A first-time visitor lands on /composer, sees other people's agents and trending tools (good — social proof), then tries to message and gets stopped by an auth dialog (ConnectDialog). Friction is real, but it's invisible until you act.

Fix. Show a thin pre-auth banner on /composer for unauthenticated users: "You'll need to connect a wallet and fund it (~$1) before sending." That makes the cost of trying explicit and lets users decide whether to invest. (Goal-Gradient Effect — when users can see the steps to the goal, they push through them.)

4.3 The 80-character placeholder dominates the search input — discover-search.tsx:38 (P2)

Currently: 'Search any agent capability. e.g. "send an email", "generate an image", "search the web", "buy a mug"...' — great as content, wrong as placeholder.

Fix. Shorten to "Describe a capability — 'send an email', 'extract a website'". Move the longer hints to a ? tooltip or render them as clickable example chips below the input (one tap to fill). Chips also nudge users toward queries that actually return results.


5. Behavior

5.1 No "Register Resource" CTA above the fold for new visitors — discover-search.tsx:60-92 (P0)

Today the "Register Resource" button only shows when the search input is empty and on desktop (hidden md:flex at line 69). Mobile users never see it, and a desktop user who types anything loses it. The supply-side audience is invisible.

Fix. Either (a) keep "Register Resource" persistently visible as a small secondary CTA next to the search button on all screen sizes, or (b) add a "For Builders" link in the global nav.

5.2 Cross-entity navigation only happens through addresses (P1)

A user on /buyer/0xA can't click "see this buyer's primary seller" — they'd have to scan transactions and click an address. Nothing on the buyer page surfaces relationships explicitly.

Fix. On Buyer pages, mirror the existing Recipient pattern: add a "Top sellers used" section above transactions (similar to BuyerSellers but more prominent). On Server pages, add "Top buyers." (Mental Model — these entities are nodes in a graph; every node should expose its edges.)

5.3 Inconsistent tabs across entity types (P1)

Entity Tabs
Server Overview
Recipient Overview · Resources · Transactions
Buyer Overview · Transactions
Facilitator Overview · Transactions

A user who learns Recipient's mental model is surprised that Server has no Transactions tab and Buyer has no Resources tab.

Fix. Standardize. Every entity gets: Overview · Transactions · [type-specific]. Server adds Resources; Buyer adds Sellers; Facilitator stays as-is. (Jakob's Law — internal consistency is just as important as external consistency.)

5.4 Goal-gradient: nothing celebrates the first action — Composer (P2)

A user funds, signs in, and makes a first call. There's no moment of "you did it." The wallet badge updates and that's it.

Fix. A small confetti or toast on first successful call: "First x402 call complete. Welcome aboard." Cheap, but well-documented to lift activation. (Peak-End Rule.)


6. Decision-Making

6.1 Discover table: 8 columns is too many — columns.tsx (P1)

Server, Activity sparkline, Txns, Volume, Buyers, Latest, Chain, Try It. (Hick's Law + Miller's Law — beyond ~7 columns, scanning rates collapse.)

Fix, in priority order:

  • Drop "Activity" sparkline (low information density per pixel; the trend is implied by Txns + Latest).
  • Move "Try It" to a row-hover action.
  • Hide "Chain" on screens < lg (it's filterable globally already).
  • Default-collapse to 5 columns: Server · Txns · Volume · Buyers · Latest. Power-users get a column toggle.

6.2 Time-range dropdown only has presets — _contexts/time-range/component (P2)

Users can pick 24h, 7d, 15d, 30d, All Time. No custom range.

Fix. Add a "Custom…" option that opens a small calendar. Low effort, high payoff for power users. (Pareto — most users will stick to presets, but the 5% who need custom drive the most value.)


7. Aesthetic-Usability

7.1 The site looks the part — strong baseline

shadcn primitives, Lucide icons, motion-based hover underlines on tabs, monospace numerics, consistent spacing. This earns a meaningful trust dividend — users will perceive minor flow issues as more usable than they actually are. Don't waste it on the next item:

7.2 Footer doesn't render outside (home)(home)/layout.tsx:45 (P2)

Composer, Server detail, Recipient detail, Buyer detail all have no footer. Inconsistent.

Fix. Move <Footer /> up to (app)/layout.tsx.


Mobile & responsive

  • Tables don't horizontal-scroll — fixed column widths can overflow on small screens. Wrap the table in overflow-x-auto and add a left-edge fade-shadow to hint at scrollability. (P1)
  • Filters likely unreachable on mobile — chain selector and time-range live in the navbar; at <sm they may be cramped. Consider a single "Filters" button that opens a bottom-sheet with all controls. (P2)
  • Nav tabs don't collapse to a hamburger — risk of overflow at small widths. Either reduce tab count (per 1.3) or wrap to scroll horizontally with no-scrollbar (already applied). (P2)

Accessibility

  • No skip-to-content link in (app)/layout.tsx. Add one. (P1)
  • Sortable headers don't announce sort state — add aria-sort="ascending"|"descending"|"none" to HeaderCell. (P1)
  • Clickable table rows lack role="button" and tabindex="0" — keyboard users can't navigate row links. (P1)
  • Address copy buttons already have aria-label ✓.

Recommended sprint plan

Sprint 1 (this week, P0 only):

  • Persist sort/page/search in URL.
  • Rewrite hero value prop (1.1).
  • Fix wallet button states (4.1).
  • Rename "Withdraw Funds" → "Add Funds" / "Manage Wallet" in Composer.
  • Move primary nav up to (app) layout (1.2).
  • Persistent "Register Resource" CTA (5.1).

Sprint 2 (this month, P1):

  • Discover table column trim + right-align numerics (2.2, 6.1).
  • Add sort indicators + aria-sort (1.4, accessibility).
  • Standardize entity tabs (5.3).
  • Cross-entity links (5.2).
  • Move banner out of stats↔table sandwich (2.1).
  • Mobile table horizontal scroll.
  • Skip-to-content + role=button on rows.

Sprint 3 (P2):

  • Cmd+K recents & pinned (3.2).
  • Custom date range (6.2).
  • Confetti / first-call moment (5.4).
  • Footer in shared layout (7.2).
  • Tighten search placeholder + add example chips (4.3).

What I couldn't audit

The browser extension wasn't connected so this audit is code-only. Not covered:

  • Real spacing, contrast, font sizes, animation timing
  • Actual mobile rendering (only inferred from JSX)
  • Any dynamic states (hover, focus rings, error toasts) end-to-end
  • Real loading durations (Doherty Threshold — under ~400ms feels instant)

A visual pass would add value, particularly on the Composer chat (streaming feel, tool-call collapse defaults) and detail pages (entity headers, charts).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions