Commit graph

749 commits

Author SHA1 Message Date
SkateSpots
e0e41ae625 feat(trades): revoke/dispute a trade record (s=revoked)
On /trades, each authored trade record gets a 'Revoke' action (with a confirm
dialog) that republishes the 31871 with s=revoked — retracting it from
reputation for a disputed or mistaken deal. The counterparty keeps their own
attestation. Revoked records are already excluded from aggregation.

Gate: 101 tests, 0 errors/warnings, boot smoke green.
2026-09-26 23:00:10 +07:00
SkateSpots
5b163c632f feat(trades): My Trades page + block-filter DM relays
- New '/trades' page: action-needed (incoming 'confirm this sale' requests as
  seller, pending confirmations as buyer) with inline actions, a reputation
  card, and the user's own trade records (role/counterparty/state) linking to
  each listing. Nav entry (auth-only) + smoke route.
- DM inbox relays (kind 10050) are now block-filtered on both read and delivery
  (closes the last relay caveat).
- AGENTS.md updated.

Gate: 101 tests, 0 errors/warnings, boot smoke green.
2026-09-26 22:56:52 +07:00
SkateSpots
78f643d7a9 feat(mints,escrow): MintRadar polish + escrow status in shared actions
- Mints page: 'Online only' toggle + result count ('N of M mints')
- Wallet page: 'Browse Cashu mints' link to /mints
- ListingTradeActions now renders the EscrowPanel for a viewer's active escrow,
  so escrow status shows on both the grid card and the detail page
- AGENTS.md project map: marketplace detail page + shared actions

Gate: 101 tests, 0 errors/warnings, boot smoke green.
2026-09-26 22:39:58 +07:00
SkateSpots
21a0390ed5 refactor(relays): route every relay consumer through useRelays()
So Local relays are used and Blocked relays are excluded EVERYWHERE, not just in
the NPool routers:
- useListingReports, useSellerInfo, useSkateRelays (read+write), useZaps
  (NIP-57 relays tag), usePurchasePro, CollectionDetailPage, CrewForm,
  useDirectMessages write fallback
- AppProvider's empty-relays defaults check is the only intentional direct reader
- AGENTS.md updated

Gate: 101 tests, 0 errors/warnings, boot smoke green.
2026-09-26 22:36:14 +07:00
SkateSpots
5d1a0e0fb0 feat(marketplace): listing detail page + shared trade/escrow actions
- New '/listing/:pubkey/:identifier' page: large gallery + lightbox, full
  description, details, seller trust + trade reputation card, and full actions
  (shared) for buyers and owners (mark sold/relist/delete)
- Extract ListingTradeActions (confirm/escrow request/ask-to-confirm/refund/
  contact + escrow dialog) and use it in BOTH the grid card and the detail page
  so there is one implementation
- useMarketplaceItem(pubkey, identifier) fetch hook
- Cards link their image + title to the detail page
- Smoke test covers /listing/:pubkey/:identifier

Gate: 101 tests, 0 errors/warnings, boot smoke green.
2026-09-26 22:30:43 +07:00
SkateSpots
c0aec3d912 chore(lint): reach zero warnings (react-refresh)
The last 7 react-refresh warnings are dev-only HMR DX from intentional
component+hook/helper colocations (EventCountdown, SortDropdown, TrustFilter,
LanguageContext, NewsletterPage). Splitting them into new files is broad import
churn for no runtime benefit, so each carries a justified file-level disable.

npm test: 0 errors, 0 warnings, 101 tests; boot smoke green.
2026-09-26 22:23:45 +07:00
SkateSpots
6def78fffe chore(lint): clear all no-unused-vars warnings (position-precise)
Removed/renamed the remaining 39 unused locals/dead code, file-by-file:
- unused destructured bindings/params (remove the binding, keep the call)
- unused single-line pure consts and dead functions/interfaces removed
- deleted the dead _FeedItem component and its now-unused imports
- amber _readChunks self-call fixed; SpotMap popup call site updated

Warnings: 182 -> 7 (only react-refresh/only-export-components remain).
no-explicit-any=0, exhaustive-deps=0, no-unused-vars=0.
Gate: 101 tests, 0 errors, boot smoke green. AGENTS.md baseline + guidance updated.
2026-09-26 22:20:21 +07:00
SkateSpots
721a5d4b67 chore(lint): clear all react-hooks/exhaustive-deps warnings
Triage per file (not a blanket sweep):
- Memoise shared getters with useCallback and depend on them:
  EventsPage/LabPage/MapPage getTrustScore+getCommentCount (+MapPage getZapTotal)
- Memoise derived values that were recreated each render: CrewChat 'messages',
  RiderCardPage 'cardData'
- Justified eslint-disable for intentional mount-only effects with a comment:
  AppProvider (seed defaults/theme), SpotMap (Leaflet init once),
  SpamFeePayment (auto-pay keyed to invoice/state, not the recreated handler)
- EventCountdown: keep the intentional 'tick' recompute dep (documented disable)

exhaustive-deps: 14 -> 0. Warnings 60 -> 46 (39 unused-vars, 7 react-refresh).
Gate: 101 tests, 0 errors, boot smoke green. AGENTS.md baseline updated.
2026-09-26 22:09:43 +07:00
SkateSpots
dc305fbc08 docs(agents): lint baseline 60 warnings + note unsafe mass unused-var fixes 2026-09-26 21:59:44 +07:00
SkateSpots
ab87b8ab83 chore(lint): remove safely-detectable unused locals
Position-based cleanup (no name collisions) of unused catch bindings and unused
single-line pure consts across 12 files. Automated destructure/prop removals
were proven unsafe (shadowed names + typed shorthand), so those remain.

Warning backlog: 76 -> 60 (39 no-unused-vars, 14 exhaustive-deps, 7 react-refresh).
Gate green: 101 tests, 0 errors, boot smoke passing.
2026-09-26 21:58:03 +07:00
SkateSpots
d1f8870379 chore(lint): remove all explicit any + sweep dead imports
- Fix all 12 no-explicit-any (NPool, NostrEvent[], LabProfile, structural
  Cluster type, explicit SkateSpot cast) -> 0 remaining
- Remove unused imports/vars across 48 files (manual + a verified codemod for
  unused import specifiers only); tsc + tests + boot smoke all green
- Warning backlog: 182 -> 76 (remaining: ~55 no-unused-vars locals,
  14 react-hooks/exhaustive-deps, 7 react-refresh)
- AGENTS.md baseline updated
2026-09-26 21:40:42 +07:00
SkateSpots
30ef511e78 feat(relays): NIP-17 DM inbox relays (kind 10050)
- RelayMetadata.dmRelays; Relays UI to add/remove, publishing kind 10050
- NostrSync fetches the user's 10050 on login (alongside 10002)
- DMs deliver gift wraps to recipients' + own DM relays (fallback: write relays)
  and read from own DM relays + NIP-65 read relays
- useDmRelays() to look up others' DM relay lists

Also fixes a latent data-loss bug: NostrSync's 10002 fetch replaced
relayMetadata wholesale, which would wipe the new local/blocked/dm lists on
login — it now merges. 'Trusted' category intentionally skipped (no distinct
behaviour -> no empty UI); documented in AGENTS.md.
2026-09-26 21:32:53 +07:00
SkateSpots
e75c4a8355 feat(relays): Local + Blocked relay categories
Add a purposeful subset of Amethyst-style relay categories (not a full clone):
- App-local 'Local relays' (never published; added to reads+writes)
- App-local 'Blocked relays' (excluded from every read/write)
- src/lib/relays.ts: resolveRelays()/useRelays() + URL normalisation
- NostrProvider req/event routers go through resolveRelays (blocklist applied)
- RelayListManager: Local/Blocked editors; NIP-65 save now preserves the new
  lists instead of overwriting relayMetadata

Deliberately omitted (documented in AGENTS.md): KeyPackage (10051, no MLS
consumer) and proxy/broadcast/private-home (no such discovery model here).
Follow-up noted: a few hooks still read config.relayMetadata.relays directly.
2026-09-26 21:26:13 +07:00
SkateSpots
a1c30cad4e feat(mints): Cashu mint directory (MintRadar) + same-origin proxy
New '/mints' page: browse Community MintRadar mints with health (online/latency/
degraded/archived), NUT count, terms, and add-to-wallet on native.

MintRadar has no CORS, so it's fetched same-origin at /api/mints/*:
- prod: Netlify proxy redirect in public/_redirects (+ 10m cache in _headers)
- dev: Vite server.proxy in vite.config.ts
This also hides users' IPs from MintRadar. Data is unofficial/rate-limited;
the page degrades to a friendly error state. Nav entry + smoke route added.
2026-09-26 21:20:19 +07:00
SkateSpots
6f28d1fe2a docs(escrow): on-device test checklist
Add a step-by-step Android verification checklist to ESCROW.md (preconditions,
advertise key -> buyer escrow -> seller redeem -> refund -> guard rails) and
point to it from the Status section and AGENTS.md. Escrow stays unverified until
every box passes on a device with tiny amounts.
2026-09-26 21:14:11 +07:00
SkateSpots
c37e21e860 feat(marketplace): buyer escrow refund after locktime
Completes the escrow safety loop: a buyer whose escrow token is past its
locktime can reclaim it from the listing card ('Refund escrow'), which redeems
the P2PK token with their refund key. Native-only, same as the rest of escrow.
2026-09-26 21:10:59 +07:00
SkateSpots
15901976ef feat(marketplace): wire Cashu escrow into the trade flow (native)
Completes the escrow feature on top of the tested core (src/lib/cashuEscrow.ts):
- useCashuEscrow: native-only hook wiring createEscrow (cashu-ts mint + NWC/WebLN
  funding), redeemEscrow, escrow-key derivation (coco keyring) and profile publish
- EscrowKeyCard (Wallet): advertise the kind-0 'cashu_escrow' pubkey
- EscrowRequestDialog (listing card): buyer locks funds (P2PK to seller, refundable
  after N days) and sends the 31872 carrying the cashu_token
- EscrowPanel + 'Redeem escrow' in the DM request banner (seller side); the panel
  is offline/parse-based so it renders on web too
- Buyer 'Escrow & request' supersedes the plain request when available

EXPERIMENTAL: mint-touching paths are unverified on a device (native-only wallet);
use small amounts first. Gate: 101 tests, 0 errors.
2026-09-26 21:08:36 +07:00
SkateSpots
26e951710e feat(marketplace): Cashu escrow core (time-locked P2PK) + design
Real, oracle-free escrow built on Cashu P2PK with a locktime + refund key:
buyer locks funds to the seller; seller redeems on delivery; the buyer can
reclaim after the deadline. Locked (not plain) tokens are what make the
cashu_token on a public 31872 safe.

- src/lib/cashuEscrow.ts: pubkey normalisation (x-only -> compressed), P2PK
  policy builder, offline token inspection (amount/mint/locktime/refund), and
  an injectable createEscrowToken mint flow (quote -> pay -> mintProofs{p2pk})
- 15 unit tests incl. the mint orchestration against a fake wallet
- Protocol: advertise a 'cashu_escrow' pubkey in kind 0 (NIP.md); token rides
  the existing 31872 cashu_token tag
- ESCROW.md: design, threat model, status, limitations
- AGENTS.md: project map + docs index updated

Wallet UI (cashu-ts + payment rail + profile publish) is native-only and still
needs on-device verification.
2026-09-26 21:01:31 +07:00
SkateSpots
8255d9d5e8 docs(agents): make AGENTS.md the maintained cross-session memory
- Add a 'this file is the ONLY persistent memory' directive: record non-obvious
  learnings in the same session, fix stale entries immediately, date claims
- Add a 'Project map' (HashRouter, custom kinds, marketplace/wallet/DM/lazy
  routes, npm test baselines, untracked-junk list)
- Fix stale facts: BrowserRouter -> HashRouter; /src/pages + /src/lib were
  understated; describe the smoke test's hash routes + crash-screen detection
- Strengthen System Prompt Management to treat AGENTS.md as long-term memory
2026-09-26 20:51:12 +07:00
SkateSpots
b91df1b4b0 feat(marketplace): trade confirmation requests (kind 31872) + fix boot smoke test
Marketplace:
- Buyer can 'Ask seller to confirm' a sold listing -> publishes a 31872
  Attestation Request (d/a/p), which the seller fulfils in one tap
- Seller sees incoming requests in the DM thread ('Confirm this sale?') and the
  fulfilling 31871 carries the spec's 'request' tag
- useTradeAttestationRequest / useIncomingTradeRequests / useListingTradeRequest
- NIP.md documents 31872 and states escrow is intentionally NOT implemented
  (coco-cashu has no P2PK/HTLC locking, wallet is native-only, and release-on-
  attestation needs a witness) so nothing looks trustless that isn't

Hardening:
- scripts/smoke-test.mjs navigated to /settings etc. WITHOUT a hash, so with
  HashRouter it only ever loaded the homepage and reported PASS. It now puts the
  route in the hash, covers settings/stash/collections/stats/marketplace, and
  fails on the 'This page slammed' error boundary (not just boot failure)
2026-09-26 20:47:29 +07:00
SkateSpots
4e17852df3 fix(routes): self-healing lazy routes; document dev dep-cache crash
Symptom: in the long-running dev server some lazy routes (/settings, /stash,
/collections, /stats) hit the route error boundary ('This page slammed') while
others worked. Console: 'Failed to fetch dynamically imported module'; network:
504 for node_modules/.vite/deps/*.js. Vite's dep-optimizer cache had gone
missing after the server re-optimized on a lockfile change, leaving stale ?v=
hashes.

- Add lazyWithRetry: on a chunk-load failure, retry once, then do a single
  full reload per session (avoids reload loops); clears the flag on success
- Use it for all 53 lazy routes in AppRouter
- Document the dev dep-cache gotcha + fix in AGENTS.md
2026-09-26 20:36:14 +07:00
SkateSpots
1577ddc86f feat(marketplace): trust-weight trade reputation + in-thread trade confirmation
- Reputation now exposes per-counterparty verified counts; the profile card
  trust-weights each confirmed trade by the counterparty's web-of-trust score
  (fresh-account rings can't inflate it) and labels Highly trusted / Building /
  New trader
- usePendingTradeConfirmations finds sales a seller recorded that name you but
  you haven't mirrored; the 1:1 DM thread shows a 'Did you complete this trade?'
  banner with one-tap Confirm
2026-09-26 20:22:37 +07:00
SkateSpots
883338f060 feat(marketplace): mutual trade attestations + reputation (kind 31871)
Built on the Attestations NIP (31871); a listing is the Assertion Event:
- Seller 'Mark as sold' now opens a dialog (buyer npub + self-reported method +
  optional amount) that marks the listing sold AND publishes a trade attestation
- Buyer sees 'Confirm this trade' on the listing and publishes the mirror
  attestation -> mutual pair = a verified trade
- useTradeReputation aggregates sales/purchases/verified + counterparties
- Profile 'Trades' card; seller 'N verified trades' badge on listing cards
- NIP.md documents kind 31871 usage + our p/method/amount extensions
Payment method is self-reported metadata, never proof of payment.
2026-09-26 20:17:38 +07:00
SkateSpots
78324605c1 feat(wallet): Lightning wallet over NWC (balance, receive, send)
The Wallet page was Cashu-only and said 'App only' on web, so anyone with an
NWC wallet connected saw nothing. Adds:
- useNWC.getBalance() (millisats -> sats) and makeInvoice() via NWCClient
- NwcWalletCard: live balance + refresh, Receive (create invoice, QR, copy),
  Send (paste invoice, decoded amount shown, pay via the connected wallet)
Renders only when an NWC connection is active.
2026-09-26 20:01:59 +07:00
SkateSpots
b1540f0608 docs: group messaging — what ships (NIP-17) and the upgrade paths (Marmot/Concord/Cordn) 2026-09-26 19:56:58 +07:00
SkateSpots
adc3a20094 feat(dm): create group DMs from the New Message picker
The people picker is now multi-select: tap to toggle (checkmark), then
'Start group (n)' opens the encrypted group thread, or 'Message' for one person.
Selecting one person still behaves exactly as before.
2026-09-26 19:55:03 +07:00
SkateSpots
2a47e5c9c5 feat(dm): group DMs (NIP-17) — inbox list + group thread
Uses the existing gift-wrap primitive (kind 14 rumor, one p-tag per recipient):
- useGroupConversations: lists multi-recipient threads, bucketed by the sorted
  participant key; the inbox shows a Groups section
- GroupConversationPage (/messages/group/:participants): encrypted group thread
  with sender names, send + error feedback
- smoke test now boots the new route

Group creation UI (multi-select in NewMessageDialog) is the next step. For
stronger guarantees (forward secrecy, rosters) Marmot/MLS or Cordn would be the
protocol to adopt later; noted in the hook.
2026-09-26 19:52:33 +07:00
SkateSpots
ca199fac6e chore(deps): Capacitor 8.4.2 -> 8.5.2 (safe minors)
npm update within existing ranges. @noble/* and @nostrify/types were held back
by peer ranges and stay put. Gate + boot test green; npx cap now 8.5.2 (a future
android:build will sync the native project to match).
2026-09-26 19:46:59 +07:00
SkateSpots
bcd99994a9 test: cover zap helpers and newsletter data
- useZapTotals: zapTargetKey (regular id vs addressable coord) and
  zapReceiptSats (amount tag -> bolt11 -> description JSON -> 0, malformed safe)
- newsletterIssues: schema, newest-first ordering, publish hashtags
2026-09-26 19:45:48 +07:00
SkateSpots
81eeba7797 perf(bundle): lazy on-demand modals in Layout; skip the Cashu chunk without a wallet
- BackupReminder, CustomizeNavSheet, SetStatusDialog and LoginDialog are only
  needed after the user acts -> React.lazy (Layout chunk 511 KB -> 486 KB)
- The Cashu balance widget now only mounts when a wallet likely exists (web:
  localStorage hint; native: always, since the seed is in the KeyStore), so
  wallet-less visitors never download the 245 KB Cashu chunk
2026-09-26 19:41:35 +07:00
SkateSpots
8cf35cf7db perf(bundle): entry chunk 1835 KB -> 724 KB
Data-driven pass (adds rollup-plugin-visualizer behind ANALYZE=1):
- MapPage was statically imported by the router, dragging the map route AND the
  IndexedDB offline-cache layer into the entry -> now lazy()
- NewsletterOverlay imported NEWSLETTER_ISSUES from the whole NewsletterPage
  module -> types + data moved to lib/newsletterIssues.ts
- SasquatchGuide (assistant overlay) lazy-loaded in Layout
- Cashu stack is now a separate chunk (useCashu), off the critical path

Net: entry down ~61%; first paint no longer parses the map, the newsletter
page, the assistant or the wallet stack.
2026-09-26 19:37:11 +07:00
SkateSpots
e6f1fe2673 perf(bundle): lazy-load the Cashu balance; load @getalby/sdk on demand
- Layout (app shell) imported useCashu, pulling the Cashu stack into the entry
  chunk every visitor downloads. The three balance widgets now live in
  components/wallet/CashuBalance.tsx and load via React.lazy.
- useNWC statically imported @getalby/sdk; it is now a type-only import plus a
  dynamic import at call sites, so the SDK only loads when NWC is used.
Entry chunk: 1879 KB -> 1835 KB.
2026-09-26 19:31:01 +07:00
SkateSpots
fb5ebb5cd4 chore(dm): remove the unused useGroupConversations hook
It was exported but referenced nowhere (only in a comment); the 1:1 inbox never
surfaced group chats. Comment updated accordingly.
2026-09-26 19:25:40 +07:00
SkateSpots
2edf7a797e feat(ui): adopt the shared PageHeader on Wallet, CreateListing, GearSwap, Messages
PageHeader now carries the app's neon glow (accent-tinted text-shadow), so the
unified header keeps the brand look rather than flattening it. Four more plain
headers migrate (title + accent + optional subtitle/actions).
2026-09-26 19:23:01 +07:00
SkateSpots
4d2c29edb0 feat(ui): PageHeader/PageContainer foundation (piloted on Contacts); profile info-first on mobile
- New shared PageHeader + PageContainer (width tokens: narrow/list/detail/wide)
  to end the max-w drift between sections; piloted on ContactsPage
- Profile: the identity/contact sidebar now comes first on mobile (order-first /
  lg:order-none) instead of being buried under every content section
2026-09-26 19:17:55 +07:00
SkateSpots
10ab45383d chore(ridercard): delete the unused AwardBadgeDialog
showAwardDialog was never set true, so the dialog could never open; the inline
AwardBadgeInline form is the live flow. Removes ~142 lines plus its now-unused
Dialog imports, state and helper.
2026-09-26 19:14:21 +07:00
SkateSpots
bf7fdbd621 fix(ux): calendar 'Past' opens on the newest month with events
The calendar always initialised to the current month, so the Past tab (all
events in the past) opened on an empty month. It now anchors to the newest
past event, and re-anchors when the tab changes.
2026-09-26 19:09:21 +07:00
SkateSpots
0aee937880 fix(a11y): remove duplicate profile status dot; raise low-contrast text
- Profile showed the live status indicator twice (avatar + name row) -> once
- Stash/Wallet informational text bumped from gray-600/700 to gray-400/500 for
  readable contrast on the near-black background
2026-09-26 19:05:35 +07:00
SkateSpots
c0ee70b8bb fix(ux): map empty state; marketplace-aware CreateListing return
- Map view now shows a 'No spots match / Clear filters' overlay when the active
  filters hide everything (it previously looked blank/unloaded)
- 'List Gear' passes returnTo=/marketplace, so the back link and the post-publish
  redirect return to Marketplace instead of dumping users on the map
2026-09-26 19:03:29 +07:00
SkateSpots
18d2af6366 fix(ux): remove dead marketplace payment dialog; show live sats estimate
The '$40k/BTC' era confirm-payment dialog was unreachable after the Pay button
was removed — deleted it, its handler and state (and the now-unused useCashu
import). In its place, listings show '≈ N sats' under the price using the live
rate, so buyers still see the sat amount.
2026-09-26 18:59:47 +07:00
SkateSpots
760bc9b82c fix(ux): Batch F part 3 — bounty ranks, Lab tab grid, contacts search
- Bounty leaderboards no longer slice to top 50 before computing the current
  user's rank (anyone outside the top 50 never saw their position), and each row
  no longer prints the count twice
- Lab craft tabs: 4-up grid (11 tabs were laid out 5-across, leaving a lone
  'Judges' orphan row); stale comment corrected
- Contacts search placeholder now says it filters by npub (it never matched the
  displayed names)
2026-09-26 18:54:55 +07:00
SkateSpots
a464913d6f fix(ux): Batch F part 2 — shop gallery lightbox; real FAQ content
- ShopDetailPage gallery reuses ImageLightbox: photos are now zoomable and the
  whole set is reachable (it showed at most 5 and clicks did nothing)
- FAQPage gains static quick answers so the page is useful even if The
  Sasquatch guide fails to open (it previously had zero intrinsic content)
2026-09-26 18:52:05 +07:00
SkateSpots
617c6cee82 fix(ux): Batch F part 1 — false 'no spots' while loading; calendar filter leak
- SpotPicker showed 'No spots found' during the initial load (also affected
  CreateListing); now shows a loading state first
- Events: switching to the calendar view clears the date-window filter, which
  only has chips in list view — it was silently hiding calendar events
2026-09-26 18:49:14 +07:00
SkateSpots
09e1612db7 fix(a11y): Batch E part 1 — labels, valid nesting, touch targets
- EmptyState: render <Link> as the button (a <button> inside <Link> was invalid
  and double-focusable); AboutPage <a href> -> <Link> (no full reload)
- aria-label on icon-only controls: settings Back, conversation Refresh/Send,
  crew chat Send
- Crew invite Accept/Decline raised to 44px touch targets
2026-09-26 18:46:51 +07:00
SkateSpots
0468ed1c1b fix(ux): Batch D part 2 — Marketplace in the nav; Clink fallback copy
- Add Marketplace to the More menu (it had no primary-nav entry)
- CLINK: explain the copy/QR fallback when no CLINK-compatible wallet handles
  the deep link, so 'Open in CLINK wallet' is not a silent no-op
2026-09-26 18:05:15 +07:00
SkateSpots
102d6017bc fix(marketplace): live BTC/USD sats estimate; drop dead Pay button
- Sats prices used a hardcoded $1 = 2500 sats (i.e. $40k/BTC), so buyers were
  quoted wrong amounts; now uses the live useUsdToSats rate, handles sats/BTC
  directly, and shows no estimate for unsupported fiat
- Removed the 'Pay with Cashu' button, which only ever showed a 'coming soon'
  toast — a dead affordance. Buyers use Contact Seller.
2026-09-26 18:02:24 +07:00
SkateSpots
dca0c0ede1 fix(ux): Batch C part 1 — Near Me timing & reachability
- geolocation: cap the overall deadline at 15s (was 25s) and shorten the native
  plugin attempt to 8s, so a failed fix no longer hangs ~25-40s
- MapPage: only warm the location cache when permission is ALREADY granted, so
  opening /map no longer fires the browser's 'Allow location?' prompt
- MapPage: add a desktop Near Me control (it was mobile-only)
- MapPage: on native, a denial is now retryable instead of permanently opening
  the browser-only help sheet
2026-09-26 17:58:38 +07:00
SkateSpots
8e35859188 fix(ux): Batch B — errors no longer masquerade as empty/404
Adds a shared ErrorState/PageError with a retry action. Detail pages (spot,
shop, collection, coach, crew, event) now show an honest failure + Try again
instead of '404 Not Found' — a relay hiccup previously implied the content was
deleted. The zap/bounty leaderboards surface errors instead of 'no content yet',
and the DM inbox gains a retry button.
2026-09-26 17:51:30 +07:00
SkateSpots
cbd12f3e52 fix(ux): Batch A part 2 — stash, crew invites, map marker
- Stash: remove the handler-less 'Share via DM' button; action overlay stays
  visible on touch (was hover-only, so edit/delete were unreachable on mobile)
- Crew invite accept no longer claims membership is confirmed (only the founder
  ratifies the roster) — both the toast and the accepted state now say so
- Crew invite share message no longer promises a one-click join
- Map marker: don't bind the Leaflet popup when a host supplies onSpotClick
  (MapPage's bottom sheet) — both used to appear at once
2026-09-26 17:47:38 +07:00
SkateSpots
f4192e2f3d fix(ux): Batch A part 1 — dead flows
- SpotDetailPage: 'Claim This Shop' was a no-op button; wire to NIP-05 settings
  (and stop the full-page <a href> reload)
- TourDetailPage: 'Contact Guide to Book' used /messages?to= which nothing read,
  so booking silently opened an empty inbox -> /messages/:npub
- ContactsPage: row 'Message' went to the generic inbox -> the contact's thread
- SettingsPage: ?section=follows|language|stash rendered a blank dead end;
  unknown sections now fall back to the menu
- DirectMessages/Conversation: logged-out states were dead ends -> add LoginArea
- Conversation: failed sends were silent -> error toast
2026-09-26 17:43:19 +07:00