8 KiB
8 KiB
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.