SkateSpots/UX_AUDIT.md

109 lines
8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SkateSpots — UI/UX Audit & Fix Backlog
Findings from a code-level audit across every area (spots/shops, lab, crews, invitations,
marketplace, stash, wallet, events, tours, calendars, messaging, community stats, profile,
contacts, near-me, rider card, settings). Each item has an evidence pointer.
Work order = top-down, one batch per change-set, shipped through the normal pipeline
(`npm test` → boot smoke test → auto-deploy).
---
## P0 — Broken flows & trust (dead controls, false claims)
| # | Issue | Evidence |
|---|-------|----------|
| 1 | **"Claim This Shop" is a dead button** (no handler) | `SpotDetailPage.tsx:512` |
| 2 | **Tour "Contact Guide to Book"** links to `/messages?to=` which nothing reads → dead CTA | `TourDetailPage.tsx:55`; route is `/messages/:npub` |
| 3 | **Contacts "Message"** goes to the generic inbox, not the contact | `ContactsPage.tsx:75` |
| 4 | **Stash "Share via DM"** has no handler; edit/delete/list are hover-only (unreachable on touch) | `StashPage.tsx:222`, `:207` |
| 5 | **Marketplace cannot be bought**: no listing detail route; "Pay with Cashu" is a "Coming Soon" stub; every card navigates straight to a DM | `MarketplaceItemCard.tsx:133-152`, `GearSwapItemCard.tsx:165` |
| 6 | **Map marker opens the Leaflet popup AND the bottom sheet** at once | `SpotMap.tsx:1125`, `MapPage.tsx:1609/1927` |
| 7 | **Near Me on native: one denial = permanent dead end** (shows *web* browser help) | `MapPage.tsx:1726` + `:723-738` |
| 8 | **Settings deep links** `?section=follows\|language\|stash` render a blank page with no back | `SettingsPage.tsx:57-70` vs `1194-1215` |
| 9 | **Events calendar silently inherits an invisible `dateWindow` filter**; Past+Calendar opens the current (empty) month | `EventsPage.tsx:1118`, `:926-929` |
| 10 | **Crew invite accept says "membership confirmed"** even when only the founder can add the member | `CrewInviteCard.tsx:38-46` |
| 11 | **Crew "Share link"** promises a one-click invite; recipient only gets a join-request | `CrewInviteDialog.tsx:251-287` |
| 12 | **Logged-out DM / conversation are dead ends** (no LoginArea), unlike Contacts | `DirectMessagesPage.tsx:171`, `ConversationPage.tsx:152` |
| 13 | **Message send has no error feedback** | `ConversationPage.tsx:126-137` |
## P0 — Errors masquerade as empty / 404
| Issue | Evidence |
|-------|----------|
| Detail pages map network errors to **NotFound** (implies deleted) | `SpotDetailPage.tsx:240`, `ShopDetailPage.tsx:188`, `CollectionDetailPage.tsx:224`, `CoachDetailPage.tsx:119`, `CrewDetailPage.tsx:245`, `EventDetailPage.tsx:272` |
| Leaderboards/pages ignore `isError` → "no content yet" | `EventsPage.tsx:814/1220/1265`, `BountyLeaderboardPage.tsx:141`, `ZapLeaderboardPage.tsx:159` |
| DM inbox error has no retry (Conversation does) | `DirectMessagesPage.tsx:278` |
## P1 — Loading / empty-state defects
| Issue | Evidence |
|-------|----------|
| Spot picker shows false "No spots found" while loading (also in CreateListing) | `SpotPicker.tsx:26,179`; `CreateListingPage.tsx:140-151,967` |
| Collection skeleton grid ≠ real grid → layout jump on load | `CollectionDetailPage.tsx:54` vs `:314` |
| Map view has **no empty state** when filters hide everything | `MapPage.tsx:1591-1868` |
| `EmptyState` nests `<button>` inside `<Link>` (invalid) | `EmptyState.tsx:67-71` |
## P1 — Near Me (geolocation) timing & reliability
| Issue | Evidence |
|-------|----------|
| **Non-denied errors (TIMEOUT/UNAVAILABLE) are swallowed** → promise only fails at the 25s deadline; native path ≈40s worst case | `geolocation.ts:328-343`, `:230-245` |
| Permission requested **on page load** without a user action | `MapPage.tsx:804-817` |
| No locate/Near Me control on **desktop** | `MapPage.tsx:1719` (`md:hidden`) |
| Crews "Near me" shows active state even when no location was obtained | `CrewsPage.tsx:198-205`, `:296` |
| Location-help overlay has no dialog semantics / Escape / focus trap | `MapPage.tsx:1669-1710` |
## P1 — Money correctness
| Issue | Evidence |
|-------|----------|
| Marketplace sats price uses **hardcoded $1 = 2500 sats** | `MarketplaceItemCard.tsx:119-121,476` |
| **Wallet page is Cashu-only + "App only" on web** — no balance/send/receive despite the name | `CashuWalletCard.tsx:57-136`, `useCashu.ts:168` |
| Cashu **backup warning never resolves** (no persisted "backed up" state) | `CashuWalletCard.tsx:702`, `useCashu.ts:100` |
| ClinkPayButton **silent no-op** without a CLINK wallet; competes with zaps | `ClinkPayButton.tsx:189` |
| Orphan `/marketplace-listings` page; marketplace missing from primary nav | `AppRouter.tsx:117/158`, `Layout.tsx:75-80` |
## P2 — Consistency (systemic)
| Issue | Evidence |
|-------|----------|
| **No shared page header / container widths** — every area differs (list vs detail) | Lab `max-w-4xl`, Crews `max-w-3xl`, Contacts `max-w-2xl`, DM `max-w-2xl`, Wallet `max-w-lg`, CreateListing `max-w-2xl`, GearSwap/Stash `max-w-7xl`, RiderCard `max-w-2xl`, EventDetail unbounded |
| EventDetail uses shadcn `primary`/`accent` tokens while the app is neon | `EventDetailPage.tsx:513/548` |
| RiderCard dead `AwardBadgeDialog` state | `RiderCardPage.tsx:1129,1639` |
| Lab craft tab grid leaves an orphan row (11 tabs, `grid-cols-5`) | `LabPage.tsx:424-455` |
| `AboutPage` / `MarketplacePage` use `<a href><Button/></a>` (full reload, nested interactive); About hardcodes stale `v0.1.0` | `AboutPage.tsx:159`, `MarketplacePage.tsx:182` |
| FAQ page has **no static content** — depends entirely on the AI guide opening | `FAQPage.tsx:42-105` |
| Profile: sidebar (info/contact) buried below content on mobile; duplicated status indicator | `NIP19Page.tsx:403,709`, `:249/264` |
## P2 — Accessibility
| Issue | Evidence |
|-------|----------|
| Icon-only buttons without `aria-label` (send, refresh, stash actions, settings back, profile actions on mobile) | `ConversationPage.tsx:288`, `CrewChat.tsx:430`, `StashPage.tsx:153`, `SettingsPage.tsx:77`, `NIP19Page.tsx:323` |
| Form labels not associated (`htmlFor`/`id`) — invite dialog, meeting point, search inputs | `CrewInviteDialog.tsx:190-218`, `CreateTourPage.tsx:1173-1186`, `GearSwapPage.tsx:227`, `StashPage.tsx:843` |
| Nested interactive elements (`<button>` inside `<Link>`) | `SpotCard.tsx:52`, `EmptyState.tsx:67`, `AboutPage.tsx:159` |
| Charts have no text alternatives; skeleton uses `Math.random()` heights | `CommunityStatsPage.tsx:316-413`, `:171-179` |
| Touch targets < 44px across many pages | `CrewInviteCard.tsx:121`, `StashPage.tsx:153`, `EventsPage.tsx:1129`, `SpotCard.tsx:186`, `ContactsPage.tsx:76`, `GearSwapItemCard.tsx:237` |
| EventsCalendar single-letter weekday headers (`S M T W T F S`) ambiguous | `EventsCalendar.tsx:233` |
## P3 — Low / cleanup
| Issue | Evidence |
|-------|----------|
| Group conversations exist but are unreachable from the inbox (dead hook) | `useDirectMessages.ts:513`, `DirectMessagesPage.tsx:153` |
| Bounty rank computed **after** `.slice(0,50)`; count shown twice per row | `BountyLeaderboardPage.tsx:156/197`, `:102-110` |
| Low-contrast informational text (`gray-600/700`) | `StashPage.tsx:260/933/1100`, `WalletPage.tsx:73` |
| Contacts search matches npub only while UI shows names | `ContactsPage.tsx:167,243` |
| Shop gallery not zoomable (Spot has ImageLightbox); share feedback inconsistent | `ShopDetailPage.tsx:217-240` |
---
## Suggested batches
- **Batch A — P0 dead flows**: items 1–13 (hide or wire every dead control; fix false crew-invite claim; DM login/error feedback).
- **Batch B — P0 error states**: replace NotFound-on-error with error+retry across detail pages & leaderboards.
- **Batch C — Near Me**: geolocation rejection semantics, defer permission, desktop control, native retry, dialog a11y.
- **Batch D — Money**: live exchange rate, wallet web UX, backup state, Clink fallback, nav/orphan page.
- **Batch E — Foundations**: `PageHeader` + container widths + touch-target + a11y primitives, then sweep areas.
- **Batch F — Per-area polish**: loading/empty fixes, calendar, profile mobile, FAQ content, cleanup.