Commit graph

314 commits

Author SHA1 Message Date
shakespeare.diy
135fbaab40 Events discovery polish + real bug/perf fixes across the grid
UX (all participants):
- EventCard now shows a live status pill (In 2 days / HAPPENING NOW / ENDED) on
  both the full and compact variants — the at-a-glance signal was previously
  detail-page only
- EventCard shows a "Presented by <sponsor>" ribbon so paid sponsor placements
  get real visibility in the discovery feed (not just after a click)
- Added a bottom legibility gradient so overlays stay readable on any cover

FIX (crash risk): EventsPage getTrustScore did `trustScores?.map.get` — since
useBatchTrustScores returns a plain Map (no `.map`), this threw TypeError once
scores loaded. Now reads the Map directly.

PERF (N+1 query storm): rendering the grid fired one RSVP query PER card
(~100 queries on a full page). Added useBatchEventRsvps — a single #a-filtered
query for all event coordinates — and thread goingCount into EventCard as a prop.
Cards without the prop (detail page, hosting dashboard) keep their own query.

Removed now-dead getRelativeTime/relativeTime/isUpcoming (superseded by the
EventCountdown pill).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 14:47:02 +07:00
shakespeare.diy
854d18c322 Give admins moderator control over events + fix a spoofing hole
Admins (the two pubkeys in src/lib/admins.ts) can now moderate any event:
- Post authoritative Event Updates (incl. cancel/reschedule) on events they don't own,
  clearly labelled "Admin" and marked with an ["admin","true"] tag
- Take down any event (already wired via DeleteEntityDialog + isAdminPubkey)

SECURITY FIX: kind-7103 Event Updates are now only trusted from the event's host
(pubkey in the `a` coordinate) or a SkateSpots admin. Previously ANY npub could
publish a fake "CANCELLED" update — spoofing the detail-page banner and spamming
RSVPers' notification bells. Enforced in both parseEventUpdate() and
useActivityNotifications.

Note: admins intentionally CANNOT open the edit form on others' events — an
addressable event (31923) can only be replaced by the key that signed it; editing
under a different key would fork it, not edit it. Moderation is done via
authoritative updates + takedown instead.

Docs: NIP.md updated with the host/admin trust rule and the admin marker tag.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 14:40:17 +07:00
shakespeare.diy
25c57cbb2f Fix event UX correctness: live countdown, cancellation banner, NIP-52 tag cleanup
- FIX: EventCountdown label never updated (memo ignored the interval tick) — now recomputes every 30s for both EventCountdown and useEventStatus
- Add loud cancelled / rescheduled banner on EventDetailPage derived from host updates; close RSVP + dim hero when cancelled so attendees don't show up to a dead jam
- Clean up non-standard NIP-52 `a` tags (['a', coord, '', ''] → ['a', coord]) in RSVP and host-update publishing
- Correct misleading kind comment in useEventRsvps

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 14:33:28 +07:00
shakespeare.diy
78f4a6d09a Host tools: attendee list w/ trust, event updates, edit-alerts, Hosting tab
- AttendeeList: tabbed Going/Interested with faces + names + a "who you trust"
  signal (reuses batch trust scores); replaces the plain avatar stack
- Event Updates (kind 7103): usePostEventUpdate + useEventUpdates; host-only
  composer with update/reschedule/relocation/cancelled categories that p-tags
  every RSVPer so they get notified; public feed on the event page
- EditEventPage: auto-post a reschedule/relocation update to notify attendees
  when the host changes time or location
- Activity bell: new 'event-update' notification type for RSVPers
- EventsPage: "Hosting" tab (logged-in) with a HostingDashboard listing the
  user's upcoming/past events, live RSVP counts, and quick edit/update actions
- Document Event Update kind 7103 in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 11:25:57 +07:00
shakespeare.diy
7439b980bb Events Phase 1: NIP-52 RSVPs, attendee stacks, date filter, host notifications
- Adopt NIP-52 RSVP (kind 31925): parseEventRsvp + RsvpStatus types
- useEventRsvps (counts by status, attendee pubkeys, my status) + useRsvp
  mutation that replaces the user's prior RSVP via a stable d-tag
- RsvpButtons (Going / Interested / Can't make it) + AttendeeStack avatar row
- EventDetailPage: "Who's going" card with RSVP control + attendee avatars
- EventCard: going count on both compact and full cards
- EventsPage: quick date-window chips (Today / Weekend / Week / Month)
- Activity bell: host RSVP notifications (new 'rsvp' activity type)
- Document RSVP schema in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 10:47:57 +07:00
shakespeare.diy
c34f194d8f Polish notifications: full activity center (social + crews), not just crews
- New useActivityNotifications: single combined #p query classifies reactions,
  reposts, zaps, mentions and replies referencing the user; parses zap sats
- New useNotificationsRead: per-account last-read timestamp for unread badge
- New ActivityNotificationCard: actor avatar + type badge, verb, snippet, links
  to the actor's profile
- Rewrite NotificationBell into a tabbed activity center (All / Social / Crews)
  with unread tracking, mark-all-read, and the existing crew invites/requests

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 10:21:15 +07:00
shakespeare.diy
f4bca7b736 Admins can endorse join-requests; founder ratifies (kind 9087)
Closes the admin/founder asymmetry without breaking the trust model: only the
founder authors the crew event, so only they add members — but an admin can now
publicly *vouch* for a join-request, and the founder ratifies with one click.

Protocol (SkateSpots-native kind 9087, regular event):
- e-tags the join-request, a-tags the crew, p-tags the requester (role
  "requester") and the founder (so it hits the founder's bell).
- Clients keep only endorsements authored by a real admin of the endorsed crew
  and dedupe one-per-admin-per-request (no credibility inflation).
- New parseCrewEndorsement + CrewEndorsement type; documented in NIP.md.

Hooks:
- useEndorseJoinRequest (admin publishes an endorsement, admin-gated).
- useJoinRequestEndorsements: batch-fetch endorsements for all visible requests
  in ONE #e query, grouped per request, admin-verified per crew.
- useCrewNotifications attaches endorsements to each request entry.

UX (JoinRequestCard):
- Founder: primary becomes "Approve & add" when vouched, with a vouch strip
  (stacked admin avatars + "Vouched by Rider +1 more") as social proof.
- Admin: the old dead-end "Only the founder can add members" becomes a real
  "Vouch for them" action, then a "Vouched — waiting on the founder" state,
  plus a one-line explainer so the path isn't a mystery.
- Endorsements enrich the existing request card (no duplicate notification).

Build clean, no console errors. Not deployed.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 01:40:04 +07:00
shakespeare.diy
0f2215f46c Sync crew-notification dismissals across devices (private NIP-78)
Fixes the caveat that dismissing a handled invite/join-request on one device
left it un-dismissed on another (it was per-device localStorage).

- New useCrewDismissals hook: stores the dismissed-id set as a single private,
  addressable NIP-78 event (kind 30078, d=skatespots:crew-notif-dismissals)
  with a NIP-44 payload encrypted to the user's own key. One list covers both
  invites and join-requests.
- useDismissCrewNotification: optimistic local update + best-effort encrypted
  publish, so dismissals feel instant and sync across every device the user
  logs in from.
- Graceful degradation: always resolves the UNION of the synced list and a
  local mirror, so it still works offline and on signers without NIP-44 (they
  just don't sync) — no regression from the old behavior.
- Rewired useCrewInvites (accept/decline/approve/dismiss) to the synced set;
  removed the two localStorage-only dismissal helpers. Filtering moved into
  useCrewNotifications so both invite + request lists share one source of truth.
- Documented the kind-30078 dismissal event in NIP.md.

Build clean, no console errors. Not deployed.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 01:32:04 +07:00
shakespeare.diy
a2c34800ce Add sponsor picker + notifiable crew join-requests
Part A — Sponsor picker:
- CrewSponsorPicker searches the shop directory and emits a 30017 coordinate
  so founders select a sponsoring shop instead of pasting a raw coordinate.
- Wired into CrewForm, replacing the raw coordinate input.

Part B — Notifiable join-requests (kind 31969):
- New addressable join-request event p-tagged to founder + admins, making the
  request→approve direction symmetric with invites (surfaced in the bell, not
  buried in a DM inbox).
- useCrewInvites: useSendJoinRequest, useIncomingJoinRequests,
  useApproveJoinRequest (founder adds member), useDismissJoinRequest (local).
- CrewRequestToJoin now publishes the structured event.
- JoinRequestCard (Add to crew / Dismiss) wired into NotificationBell and
  counted in the unread badge.
- Documented kind 31969 in NIP.md alongside invites.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 00:23:55 +07:00
shakespeare.diy
50d8d9e614 Wire live Cordn encrypted crew chat over the coordinator (ContextVM)
The crew coordinator is no longer just a pointer — when a crew configures one,
the Chat tab runs through it for real.

- src/lib/cordn: clean transport/crypto split so an MLS (ts-mls) upgrade is a
  drop-in swap of the crypto layer only.
  - types.ts: CordnMessage/CordnEndpoint + envelope kind 4497 + app tag.
  - crypto.ts: sealGroupMessage/openGroupMessage — real NIP-44 group encryption
    (per-recipient sealing), the exact seam MLS encrypt-to-epoch slots into.
  - client.ts: buildEnvelope/publishEnvelope/fetchGroupMessages with per-group
    cursor ordering and ciphertext→author sender binding (anti-spoof).
  - endpoint.ts: resolve a crew's `coordinator` to a usable relay endpoint
    (wss/ws direct; https falls back to the ContextVM relay).
- useCordnChat: fetch-then-poll hook mirroring the NIP-17 chat API.
- CrewChat: uses Cordn when the crew has a coordinator, NIP-17 fallback
  otherwise; header badge shows the active transport (Cordn vs Private).
- CrewCoordinatorCard: reflects that private chat is live.
- NIP.md: document envelope kind 4497, delivery (fetch-then-poll + cursors),
  the nip44-group sealed content, and the honest crypto→MLS seam.

Real encryption, real coordinator routing, non-breaking (fallback intact),
with the MLS ratchet as a clearly-marked next step.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 00:15:22 +07:00
shakespeare.diy
e8c1fbc71a Add crew invitation & notification system (Phase 1)
- Upgrade CrewInviteDialog with a "Send invite" tab: invite a specific npub
  with live invitee preview, optional note, and founder-only "invite as admin"
  toggle. Preserves the shareable-link flow on a second tab.
- Add NotificationBell to desktop + mobile headers: unread badge, popover with
  incoming crew invites (accept/decline) and pending-confirmation crews
  (confirm with public/crew-only visibility).
- Wire useSendCrewInvite / useMyCrewInvites / accept / decline hooks and the
  useCrewNotifications aggregator into the UI.
- Membership stays unforgeable (founder p-tag + invitee NIP-32 attestation).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 22:59:34 +07:00
shakespeare.diy
dbae3a1501 Add soft NIP-05 verification for skaters (open directory + "Verified" filter)
Keeps the skater directory fully open — NIP-05 is a trust signal, not a gate:
- Extract reusable NIP-05 verification into lib/nip05.ts (parse, fetch well-known, verify against expected pubkey); refactor useCrewNip05 to use it
- New useSkaterVerifications hook: batch-fetches kind-0 metadata for all skaters and verifies each nip05 resolves to its own pubkey, returning a Set of verified pubkeys
- SkatersPage: add a "Verified (N)" toggle (disabled when none verified) that filters to genuinely verified skaters; show ✓ badge only for truly-verified skaters; verified-aware empty state
- NIP19Page profile: nip05 line now reflects real verification (lime ShieldCheck + "Verified" pill when the handle resolves to the key, muted grey otherwise)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 21:51:11 +07:00
shakespeare.diy
b42c234709 Add Skater profiles (kind 35000) + /skaters directory
- New app-native Skater Profile kind 35000 with `skateskater` marker tag,
  the anti-spam boundary that keeps generic kind-0 Nostr profiles out of the
  directory (queried only from the community relay).
- Skater type, stance/style/level constants, and parser in types.ts.
- useSkaters + useUserSkaterProfile hooks.
- /skaters directory page: immersive hero, live count, search, style filter
  chips, ranked skater grid with NIP-05 verification badges and lazy author
  metadata.
- /become-skater opt-in flow (bio, photo, styles, stance, level, years, home
  spot, location w/ geohash, socials).
- Wire "Skaters" into the mobile nav and a cross-link banner in The Lab so
  plain skaters finally have a home.
- Document kind 35000 in NIP.md.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 20:58:51 +07:00
shakespeare.diy
b3a49499bb Rename "The Quiver" to "Stash" for brand consistency
Use the established "Stash" term everywhere instead of introducing a new
word. The profile section is now titled "Stash", visibility copy reads
"Shown in your public Stash on your profile", and internal identifiers
were renamed (useUserQuiver → useUserPublicStash, QuiverItem →
PublicStashItem, parseQuiverItem → parsePublicStashItem). NIP.md updated
to match.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 19:41:45 +07:00
shakespeare.diy
32a3cbca88 Public Stash items: "The Quiver" showcase + marketplace hybrid
Previously the "Public" visibility on Stash items did nothing — the
whole Stash is a single NIP-44 encrypted blob, so nobody could ever see
a "public" item. This makes Public actually mean public.

Model (hybrid A + marketplace):
- Public items are now ALSO published as separate unencrypted NIP-99
  kind 30402 events tagged `t: stash-showcase`, keyed by `stash-<id>`.
- Public + not-for-sale → showcase-only (`showcase: true`, price 0):
  shows on the profile "Quiver", filtered OUT of the sellable market.
- Public + for sale/trade → full listing: shows on the profile Quiver
  (badged For Sale/Trade) AND flows into the marketplace.
- Going private or deleting an item publishes a NIP-09 deletion for its
  public event, keeping everything consistent.

Changes:
- New hook useStashShowcase (sync/unpublish + useUserQuiver query).
- StashPage wires add/edit/remove through sync handlers; honest
  visibility copy in add + edit dialogs.
- Profile page (NIP19Page) renders a new "The Quiver" grid with
  For Sale/Trade badges + prices, and a Gear count in Contributions.
- Marketplace hooks skip showcase-only events so they don't clutter
  buy/sell listings.
- NIP.md documents the showcase event schema.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 19:33:34 +07:00
shakespeare.diy
407ba40a33 Sasquatch: full public-data coverage (all Lab crafts + shops + tours)
- findCoaches now covers EVERY Lab craft (coach, filmer, editor, photographer,
  builder, artist, graffiti, tattoo, trainer, musician, judge) via role param;
  reads skills from `t` tags and strips self-tags.
- New findShops (kind 30017, t=skateshop) and findTours (kind 30823) tools.
- getCommunityStats counts all crafts individually + lab_total + shops + tours,
  and filters spots to t=skatespot (drops foreign kind-30818 events).
- Wired new tools + expanded role vocabulary into the Sasquatch brain: system
  prompt, intent parser, keyword detector, and both dispatch/format paths.
- Privacy preserved: the public CVM data layer never queries the encrypted
  Stash (kind 30001) — verified no 30001/decrypt references. Personal 'my
  stash' stays login+Pro gated and decrypts locally with the user's own key.
- Normalized useCVMClient.ts to clean UTF-8 (comment glyphs only; no code change)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 16:14:20 +07:00
shakespeare.diy
8ae852f1b8 Add Judges tab to The Lab
- Flesh out SkateJudge as a full craft-based Lab profile (parseCraftBase),
  with JUDGE_FORMAT / SPECIALTY / LEVEL label sets covering Game of S.K.A.T.E.,
  best trick, jams, contests, am series, world tour stops, etc.
- Wire judge into useLabProfiles (union type, self-tag, parse case)
- Add "judging" ServiceType to LabPage: tab, accent, filter sections, empty
  state, counts, profile card icon, and ?craft=judging deep link
- New BecomeJudgePage + /become-judge route + FAB entry
- CoachDetailPage renders judge profiles (accent, icon, labels, noun, title)
- Community Stats "Judges" card now links to /lab?craft=judging
- Document kind 35012 (skatejudge) in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 15:30:43 +07:00
shakespeare.diy
9cefa29640 Make community stat cards clickable to view full lists
- Wire `to` links on stat cards: spots/shops/tours → map, events → /events,
  mappers → /bounties leaderboard, Lab crafts → /lab?craft=<type>, crews → /crews
- Stats already read from the canonical goskatespots relay and the map limit
  matches (2000) so numbers stay consistent with what's on the map

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 15:17:45 +07:00
shakespeare.diy
94a88b2ba0 v1.2.1: Community stats now show ALL Lab crafts + count every craft type
- useGrowthStats now queries and exposes every Lab craft individually: artists,
  graffiti, tattoo, trainers, musicians and judges (previously folded into an
  opaque "other" bucket with no visible breakdown).
- CommunityStatsPage renders a card for all 11 Lab crafts, so the section matches
  what /lab actually lists instead of only showing 5 categories.
- labTotal + the content-mix donut's "Lab" slice now sum every craft, keeping the
  "total" badge and chart consistent.

Note on relay mismatch: the app dedupes addressable events (keeps newest per
pubkey+kind+d) and merges across the community relay + the user's relays, so
counts can legitimately differ from a raw single-relay browser that shows stale
duplicate versions.

Bump version to 1.2.1 (versionCode 10201).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 15:07:13 +07:00
shakespeare.diy
677ccb8a82 v1.2.0: Spedn label, multi-relay community stats, GrapheneOS Near-Me fix
1. Map filter: renamed the "Bitcoin" toggle to "Spedn" (matches the toolbar
   button); tooltip now says "Spedn your sats".

2. Community stats (/stats): useGrowthStats now queries the same relay set as the
   rest of the app (community relay + user relays) via useSkateRelays instead of
   only the single hardcoded SKATE_RELAY. Lab profiles / spots / etc. that live on
   a user's own relay are now counted, so the numbers match what /lab shows.

3. Near Me on GrapheneOS: even after granting the OS location permission, the
   WebView's navigator.geolocation stays blocked → "permission denied". Fixed by
   fetching the position from the native Capacitor Geolocation plugin first
   (LocationManager, honours the granted permission), falling back to
   navigator.geolocation only if that fails. Also auto-inject
   ACCESS_FINE/COARSE_LOCATION into AndroidManifest via the manifest script.

Bump version to 1.2.0 (versionCode 10200).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 14:54:00 +07:00
shakespeare.diy
c6372742a9 Fix Amber native login in APK: reliable Capacitor detection (v1.1.7)
Root cause: LoginDialog and useCurrentUser detected the native app via
window.Capacitor, which is unreliable inside the WebView (false at mount / not
attached in some builds). So the ZapStore APK fell into the web intent:// redirect
flow and never opened Amber ("Waiting for Amber…" hang).

Fix: add isNativeAmberAvailable() in capacitorAmberSigner.ts using the imported
@capacitor/core Capacitor + native plugin presence, and use it (evaluated at
click time) in LoginDialog.handleAmberLogin and in useCurrentUser's signer
selection. The APK now calls getAmberPublicKeyViaPlugin() → native intent → Amber
opens directly, no redirects.

Bump version to 1.1.7.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 14:00:39 +07:00
shakespeare.diy
ea732fc386 Fix Sasquatch "Learn More" landing on home instead of /onboarding
The guide's action buttons used window.location.href = '/onboarding', a hard
full-page navigation. In the SPA/native (Capacitor) context this reboots the
app at its entry point (home) rather than resolving the client-side route, so
tapping "Learn More" dumped users back on the home page.

Switched all Sasquatch guide navigation (Learn More, Login to begin, Login to
Offer Services / Create a Crew, Start exploring) to React Router's navigate(),
which performs proper in-app client-side routing and reliably renders the
target page.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 11:20:53 +07:00
shakespeare.diy
adc90caae9 #2 Silence expected 'no follow list' warn in useBatchTrustScores
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 10:19:23 +07:00
shakespeare.diy
3fd82f2425 #2 Reduce console noise: strip debug logs from relay queries, publish, trust score, weather, coaches, NostrSync
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 10:17:01 +07:00
shakespeare.diy
2fa85c2bce Remove non-functional NoGood Radio everywhere (unused hook, i18n keys, onboarding refs)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 10:04:50 +07:00
shakespeare.diy
13934c0e74 Fix newsletter admin gating: use isAdminPubkey from lib/admins instead of undefined NEWSLETTER_ADMIN_PUBKEYS
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-20 16:16:09 +07:00
shakespeare.diy
82a74e01e0 Extend admin takedown to Lab profiles and gear/marketplace listings
Completes the admin moderation coverage from the previous commit.

Lab profiles (coaches, filmers, editors, photographers, builders, artists,
graffiti, tattoo, trainers, musicians):
- useLabProfiles / useLabProfile and useCoaches / useCoach now filter out
  admin-hidden entries (list) and 404 hidden ones (detail) via the shared
  moderation layer. Added filterAdminHiddenMixed() for the mixed-kind Lab
  union (kind read from each item's event).
- CoachDetailPage (the shared Lab detail page) now shows the delete/takedown
  card to admins too, not just the owner, with admin-specific copy.
  DeleteEntityDialog already handles the owner-or-admin logic.

Gear / marketplace listings (NIP-99, kind 30402):
- useMarketplaceItems / useMarketplaceAtSpot filter out admin-hidden listings.
- GearSwapItemCard: admins get a "Remove (admin)" action in the card menu with
  a confirmation dialog that routes through useDeleteEntity (NIP-09 delete +
  hidden label). Report stays for non-owners.

SECURITY.md: updated the admin-moderation coverage list and auditor file map.
Build + lint pass, no console errors.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-18 19:17:16 +07:00
shakespeare.diy
13eece3a1e Admins can delete/take down any listing (spot, crew, tour, event, shop)
The two SkateSpots admin pubkeys (already in src/lib/admins.ts) can now remove
any listing, not just their own. Because a NIP-09 deletion signed by a
non-author isn't honored by most relays, admin takedowns are made reliable with
a client-side moderation layer:

- useDeleteEntity: owners still publish a plain NIP-09 kind:5. Admins acting on
  someone else's content publish the kind:5 AND a NIP-32 kind:1985 `hidden`
  label referencing the target coordinate/id, authored by the admin.
- useAdminModeration (new): fetches admin-authored `hidden` labels app-wide and
  exposes a fast lookup + filterAdminHidden() helper. Only labels whose author
  is in the hardcoded ADMIN_PUBKEYS are honored (author = trust boundary,
  re-verified client-side; never derived from a d-tag).
- Wired the hide filter into the list + detail hooks for spots, crews (incl.
  by-focus), tours, events, and shops via React Query `select`, so removed
  content disappears from lists and 404s on its detail page.

UI:
- DeleteEntityDialog now renders for owner OR admin, with distinct
  "Remove (admin)" framing and copy (used on Crew, Coach, Tour, Event pages).
- SpotDetailPage & ShopDetailPage: routed their inline delete through
  useDeleteEntity, show the delete button for admins, and relabel the confirm
  dialog for admin takedowns.
- Added the delete control to Tour and Event detail pages (previously none).

SECURITY.md: documented the admin moderation trust boundary and extended the
auditor file map. Verified build + no console errors.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-18 17:51:34 +07:00
shakespeare.diy
b7e932d514 Fix crew detail page 404 with robust d-tag fallback lookup
useCrew previously relied on a single combined filter (kind + #d + authors,
limit 1). Some relays don't reliably match combined author + d-tag filters,
causing the crew detail page to render NotFound even when the crew exists.

Now, if the precise addressable lookup returns nothing and the author pubkey
is known, we fall back to fetching that author's crews (kind + authors) and
matching the identifier client-side. This survives relays with quirky tag
indexing while still preferring the exact match when available.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-16 17:44:52 +07:00
shakespeare.diy
96f7c3192d Interconnect Music ↔ Events: featured musician on events + gig list on musician profiles; count new Lab crafts in growth stats; document visibility/vouch/gig conventions in NIP.md
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-10 22:02:22 +07:00
shakespeare.diy
926470a86e Crews: opt-in member visibility + Lab vouch/web-of-trust anti-fraud + CrewDetail redesign
Step 3 — member visibility (best-of-both, honest):
- MemberVisibility ('public' | 'crew') stored on the member's OWN kind-1985
  attestation via a `visibility` tag, so a public crew can contain a private
  member. Confirmed crew-mates + founder see them; outsiders see anonymised
  "hidden member" slots. Framed honestly in-UI (app-level discovery privacy).
- VisibilityPicker in ConfirmMembershipCard (choose on join) +
  MemberVisibilityControl for confirmed members to change it anytime.
- useCollectiveMembers parses visibility; CollectiveMembers hides crew-only
  members from non-members.

Step 4 — anti-fraud vouch layer:
- useVouches/useVouchFor: NIP-32 vouch labels, trust-WEIGHTED via web-of-trust
  so fake-account vouch rings carry no weight. VouchCard with new/vouched/
  scene-verified tiers, wired into every Lab profile detail page.

Step 4 — CrewDetail UX redesign:
- Activity consolidated into clean Feed/Events/Chat tabs (no more giant scroll)
- Sidebar reordered: Members → Support → Band → Connections → Founder
- Member visibility control surfaced; tighter hero

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-10 21:01:57 +07:00
shakespeare.diy
0071bcba36 Lab: add Designer/Artist, Graffiti, Tattoo, Trainer/Physio & Musician crafts (kinds 35007–35011)
- New addressable kinds 35007–35011 with types, label maps and parsers
- Shared parseCraftBase helper + parseLabProfile dispatcher in useLabProfiles
- Reusable, config-driven CraftProfileForm (polished shared shell, per-craft
  skill sections + extra fields, entity/collective, CLINK, Pro upgrade)
- Five thin Become* pages wired into AppRouter and the Lab "Offer" menu
- LabPage: extended ServiceType, accents, scrollable tab strip, filter
  sections, counts, empty states and ProfileCard to all 10 crafts
- CoachDetailPage renders all craft types (map-driven accents/icons/labels)
  plus craft-specific fields (studio, books-open, credentials, remote,
  graffiti crew, music links)
- NIP.md documents all five new craft schemas

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-10 20:28:59 +07:00
shakespeare.diy
433f59415b Enrich profiles (crews, bot, CLINK), best-in-class New Message picker, map toolbar declutter
Profile page (NIP19Page):
- Add "member of crew X" summary under the header (with founder crown)
- Add a full Crews sidebar card listing every crew the user founds/belongs to
- Add crews count to the Contributions stats (now Spots/Shops/Crews)
- Show a Bot badge for kind 0 bot accounts
- CLINK "Tip in Bitcoin" already surfaces on profiles (from prior work)
- New useUserCrews(pubkey) hook powering the above

Messages:
- Replace paste-only "New Message" dialog with NewMessageDialog: a searchable
  people picker (crew-mates + following, grouped, name-searchable) that also
  accepts a pasted npub/hex as a fallback. Starting a DM now feels like a
  real contacts screen.

Map toolbar consistency:
- Consolidate secondary add actions (Shop / Gear / Tour) into a single
  "+ More" popover; keep primary "Add Spot" prominent. 4 buttons -> 2 controls.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 21:36:42 +07:00
shakespeare.diy
d465eabd87 ux(crews): rename "Start a Crew" -> "Create Crew" + clarify Crew vs Lab
"Start a Crew" was ambiguous (team? chat? listing?). Renamed to the clearer
"Create Crew" across the Crews page button/empty state, Create Crew page
title/header/login gate, and Sasquatch FAQ. Added a subtitle distinguishing a
Crew (skate WITH) from The Lab (hire).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 20:02:17 +07:00
shakespeare.diy
0ac5c1019d fix: eliminate all render-path nip44 getter crashes (Browser extension not available)
Root cause: NUser's extension signer exposes .nip44 as a getter that calls
.nostr (window.nostr) and THROWS when the extension isn't present. Any
synchronous access during render crashes the whole tree — the actual culprit
was useStash's `isEncryptionAvailable: !!user?.signer?.nip44` in its return
object, evaluated every render even when logged out.

- Add src/lib/signerCapabilities.ts: hasNip44/hasNip04 helpers that determine
  capability via login type + direct window.nostr probe (no throwing getter).
- useStash: use the helper for hasNip44 + isEncryptionAvailable
- useDirectMessages: replace all render-path `enabled: !!user?.signer?.nip44`
  with a pre-computed nip44Available across all 4 conversation hooks
- CrewChat, CrewRequestToJoin, ConversationPage, DirectMessagesPage,
  ContactsPage: use the safe helper in render/guard paths

Async accesses (inside queryFn/callbacks) are left as-is — they reject
gracefully within react-query and never crash render.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 19:37:08 +07:00
shakespeare.diy
73f51662af fix(stash): avoid nip44 getter entirely — check login type + window.nostr directly
The NUser extension signer's .nip44 getter calls .nostr which throws
"Browser extension not available" when window.nostr is absent. A try/catch
around ?.nip44 doesn't work because esbuild optimises it away.

Fix: check login type + window.nostr directly (no getter, no throw) to
determine nip44 availability, and wrap all actual signer accesses at
call-time in their own try/catch.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 19:21:28 +07:00
shakespeare.diy
4c4e7159ba fix(stash): guard nip44 getter to prevent crash when no signer/extension
Some signers throw "Browser extension not available" when the .nip44
getter is accessed rather than returning undefined. Wrap the check in a
try/catch so useStash never throws on mount for logged-out users or users
with signers that don't support NIP-44.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 19:15:44 +07:00
shakespeare.diy
ef9bbea465 feat: replace Geyser with Angor across the entire app
Remove all Geyser.fund references and replace with the SkateSpots Angor
project (angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z).

- SupportersPage: remove Geyser leaderboard + API calls entirely; replace
  Supporters section with an Angor CTA card including the on-chain wallet
  address (bc1q2hdfyqa7utrxrd95rlr27shsdr6gtk7dzdqres)
- useGrowthStats: delete useGeyserStats hook + GEYSER_API_URL constant
- CommunityStatsPage: remove Geyser stats widget, replace with Angor CTA
- SasquatchGuide: update system prompt + FAQ (is it free, supporters)
- NewsletterPage: update both Geyser links (body + footer) to Angor
- AboutPage: remove Geyser row, update Angor entry with real project URL
- README.md: update crowdfunding section + credits

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 13:23:55 +07:00
shakespeare.diy
d6b50de349 feat(sasquatch): Pro receipt DM + local Pro FAQ + tougher free-tier answers
1. Pro purchase confirmation: on a paid pass, send the buyer an encrypted
   NIP-17 receipt to their own inbox (tier, expiry, unlocked perks). Best-effort
   so it never blocks the purchase. Success toast now points to the DM.

2. Frictionless Pro answers: add local static-FAQ entries for "sasquatch pro",
   "pro perks/version", pricing, "go pro", "upgrade" — so those questions are
   answered instantly and never dead-end when the free AI model is rate-limited
   (the exact failure seen when asking "what perks has the pro version?").

3. FAQ matching now prefers the longest matching key so specific phrases win
   over broad ones ("sasquatch pro" > "pro").

4. Refresh the free-model fallback list (add deepseek-v3, gemini-2.0-flash-exp,
   qwen-2.5-72b) for better free-tier AI availability.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 00:21:34 +07:00
shakespeare.diy
1c18163a90 chore(sasquatch): remove joke book, clean up default suggested questions
- Delete sasquatchJokes.ts and all joke usages (idle chat line, fallback
  taglines, Lab/Crew guide messages).
- Tidy the default/generic suggested questions to a clean Nostr-focused set
  (Plan a trip, spots, events, crews, zaps, Nostr, Stash, free).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 00:08:52 +07:00
shakespeare.diy
1efeae0960 feat(sasquatch): add Trip Planner + tougher free-tier fallback + new joke book
- Trip Planner: `plan_trip` tool orchestrates spots+events+coaches+crews+weather
  into a weather-aware, day-by-day itinerary card (TripItineraryCard). Actions:
  save all spots to a NIP-51 Collection, book/zap a coach via their profile,
  and deep-link out to flights/stays (assist, never fake-book, no KYC).
- Free-tier resilience: local intent parser answers "spots/events/coaches/
  crews/gear/weather in <city>" and full trip plans directly via CVM even when
  both AI providers are rate-limited — no more dead-end fallback.
- New shared joke book (sasquatchJokes.ts): skate humor + gentle roasts of the
  KYC-your-audience Bitcoin-influencer crowd. Idle joke on the empty chat state,
  ethos jokes in fallbacks. Dedupes the old inline joke arrays.
- UX polish: taller chat scroll area, trip-planner suggested questions, FAQ
  entries for the planner.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 00:04:28 +07:00
shakespeare.diy
5655fbc976 Sasquatch Pro Phase 2: action drafting (draft → you confirm)
Sasquatch can now DRAFT actions from natural language and publish them
only after the user reviews & confirms — it never publishes on its own.

- useSasquatchActions: publishes confirmed drafts via the app's normal
  publish path. Supports post_status (NIP-38 kind 30315, auto-expiring)
  and list_gear (NIP-99 kind 30402, skateboarding-tagged), reusing the
  existing event schemas.
- SasquatchActionCard: inline "review & confirm" card with editable key
  fields (status text / listing title, price, condition), Publish +
  Cancel, and done/cancelled states. Nothing is signed until Publish.
- SasquatchGuide: new draft_action tool (Pro-gated). The AI emits a
  draft; a card renders in-chat; confirmDraft signs & publishes. Non-Pro
  users get an upgrade nudge. Draft lifecycle tracked per message.
- Prompt teaches the model to use draft_action for "post/sell/list/set
  my status" requests and to fill gear fields from the stash first.

Examples now working (Pro): "post that I'm skating Southbank for 3 hours"
· "list my Spitfire wheels for 8000 sats".

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-29 23:41:11 +07:00
shakespeare.diy
79d1d47823 Sasquatch Pro Phase 3: npub-bound Lightning paywall + gating
Adds the Sasquatch Pro supporter pass with strong, forge-resistant
verification bound to the paying user's npub — no backend required.

Verification (useSasquatchPro):
- STRONG: NIP-57 zap receipt (kind 9735). Purchase signs a zap request
  tagged pro-tier/pro-expiry; the receipt embeds it, cryptographically
  binding payment → identity (unforgeable by the user).
- FALLBACK: self-signed kind-32671 receipt so paying users are never
  locked out if the provider emits no zap receipt.
- Platform owners always Pro. Access = max unexpired expiry.

Purchase (usePurchasePro):
- Zap to skatespots@minibits.cash (falls back to plain invoice), pay via
  NWC → WebLN → manual QR. Publishes the fallback receipt on success.

Pricing (sasquatchPro.ts): 4 tiers — 7d/2k, 1mo/5k, 3mo/12k (popular),
1yr/36k sats.

UI (SasquatchProDialog): upgrade sheet with tiers, perk list, QR
fallback, "already Pro" state, extend flow.

Gating (SasquatchGuide):
- Personal tools (get_my_*) now require login + Pro; prompt only exposes
  them to Pro users; tool switch guards with a pro_required nudge.
- ⚡ Pro badge when active, ⚡ Go Pro button otherwise; local upgrade
  nudge for "my/mine" questions from non-Pro users.

Docs: kind 32671 + verification model documented in NIP.md.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-29 23:33:39 +07:00
shakespeare.diy
a37224d8a2 Sasquatch Pro Phase 1: read-only personal assistant tools
Sasquatch can now answer questions about the logged-in user's OWN data
("what's in my stash?", "my crews", "events near my crew", "my spots",
"what am I selling?").

- New usePersonalContext hook: client-side getters for the user's Stash
  (decrypted locally), crews (founder/member), marketplace listings, and
  created spots. Reads own data only — nothing personal leaves the device.
- Registered 4 personal tools (get_my_stash / get_my_crews /
  get_my_listings / get_my_spots) in the Sasquatch tool switch.
- Personal-tools prompt block is appended to the system prompt ONLY when
  a user is logged in, so logged-out UX is unchanged.
- needsLiveData now routes "my/mine" questions to the AI tool path.

Phase 1 is intentionally ungated so we can validate the experience
before wrapping the Pro paywall (Phase 3).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-29 23:21:37 +07:00
shakespeare.diy
e6517b5c70 Add owner-only ContextVM server health check (Settings > Advanced)
Probes the ContextVM relay for the CVM server's own events and reports
online/stale/offline with a last-seen timestamp. Gated to the two
platform owner pubkeys (renders null for everyone else). Notes that
Sasquatch's data layer queries relays directly and is unaffected by the
server being down.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-29 22:27:22 +07:00
shakespeare.diy
148ae251ef Add desktop top-nav customization (Settings > Appearance > Navigation)
Option B: a lightweight reorder / show-hide list for the desktop header.

- useDesktopNavPreferences: separate localStorage-backed preference for which
  catalog items appear as primary top-nav tabs and their order (Map fixed
  first, no hard slot cap). Reuses the shared NAV_CATALOG.
- DesktopNavManager: settings UI to reorder primary tabs (up/down) and toggle
  items between the top bar and the "More" menu; Map locked; reset to default.
- Layout desktop header now renders fixed Map + user's primary tabs, and the
  "More" dropdown dynamically lists the hidden catalog items (auth-gated),
  so each item appears in exactly one place.
- Manager shown only on md+ screens (mobile uses the bottom-bar customizer).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-29 22:09:20 +07:00
shakespeare.diy
a46be4be68 Fix generic npub names: query profile aggregators in parallel in useAuthor
Previously useAuthor only hit the global profile-aggregator relays as a
fallback when the main pool returned zero kind 0 events. On the curated skate
relay many followed users have no profile, so names fell back to genUserName.
Now query the main pool and aggregators (purplepag.es, nostr.band, nos.lol,
damus) in parallel and keep the newest kind 0, so real display names resolve.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 14:01:55 +07:00
shakespeare.diy
d7af2626ff Customizable mobile bottom nav: fixed Map + 3 user-picked tabs + fixed Profile
- Add useNavPreferences hook (localStorage) with a curated catalog of pinnable
  destinations (Lab, Events, Crews, Wallet, Stash, Collections, Contacts,
  Messages, Stats), defaults, normalization/validation and login-gating
- Map (Shops & Spots) is fixed on the left; Profile/Login fixed on the right;
  the 3 middle slots are user-customizable — nothing is ever lost since the
  burger menu still lists everything
- CustomizeNavSheet: bottom-sheet editor with a live preview bar (locked slots
  shown with 🔒), a toggle grid with a 3/3 counter + "bar full" state, up/down
  reordering, and reset-to-default. Draft state so changes are cancelable
- Entry points: long-press (or right-click) any bottom tab, plus a "Customize
  nav bar" item in the burger menu
- Login-gated tabs are hidden from logged-out users and gracefully fall back if
  a saved layout references them
- Resolve tabs in Layout and pass to MobileNav as props (avoids any module-init
  ordering issues with the hoisted MobileNav declaration); defensive ?? [] guard

Docs unchanged (client-only preference, no new Nostr kinds).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 12:53:06 +07:00
shakespeare.diy
6848a72869 Crews: proximity discovery, activity/rep signals, treasury, hiring pipeline, crew-hosted events + reverse links; add Contacts page
Crews page
- "Near me" geolocation toggle with haversine distance sort + distance chips
- Crew count + cities social-proof stat header
- Deterministic per-crew vibe-colour accents (no more wall of teal)
- Activity pulse ("🔥 active this week") + OG/Verified/Established rep badges
- Rich empty state ("Claim your local scene") instead of dead "No crews yet"

Crew synergies
- Crew treasury: transparent, non-custodial NIP-75 zap goal (kind 9041) with
  live progress bar tallied from zap receipts; funds settle to founder's own
  Lightning wallet — no shared wallet or new wallet settings needed
- Crew-hosted events: host a jam/comp as the crew (event a-tag → crew coord),
  "Hosted by [crew]" on event pages, Crew Events list on the crew page
- Crew ↔ Lab hiring pipeline: "Open Call / looking-for" flags (filmer, editor,
  coach, builder, members) surfaced on the crew page
- Request to join: encrypted NIP-17 DM to founder + admins (no founder-push
  bottleneck, no public join event)
- Reverse links: "Crews that skate here" on spot pages

Contacts (0xchat-style + Armada-style privacy)
- New /contacts page with Crew-mates / Follows / Recent DMs / Saved tabs
- Per-contact quick actions: message, zap, view profile, star
- Private encrypted contacts list (NIP-51 kind 30000, NIP-44 encrypted to self)
- Added to burger menu + desktop More menu (gated to logged-in users)

Docs: NIP.md updated with crew treasury, crew-hosted events, looking-for tags,
and the private encrypted contacts list.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 12:38:45 +07:00
shakespeare.diy
a9e0c1dcb7 Add community Spot Status Reports (kind 2259)
Solves the "spots that change appearance or disappear" problem with
crowd-sourced, trust-weighted status reports — since the spot owner
often isn't the one who notices a spot got knobbed/demolished/busted.

New custom kind 2259 (regular, append-only): any user can report a
spot's current physical state — active / bust / skate-stopped / closed /
gone — with an optional note, referencing the spot via its `a`
coordinate.

Client aggregation (useSpotStatus / useBatchSpotStatus):
- recency-weighted (30-day half-life) so fresh reports dominate and a
  rebuilt spot naturally recovers
- trust-weighted via existing useBatchTrustScores (0.2–1.0 multiplier)
- most-recent-report-per-reporter (no ballot stuffing)
- reports older than 180 days ignored

UI:
- SpotStatusBadge (colored consensus badge + tooltip w/ report count)
- ReportSpotStatusDialog (status picker, optional note, recent reports)
- Badge + Report Status button on SpotDetailPage
- Batched status badge on SpotCard; "gone" spots dimmed/grayscaled
- "gone" spots hidden from map markers (still listed dimmed) to keep the
  map uncluttered while preserving the record

Documented the kind, tags, status values, and aggregation rules in
NIP.md.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 23:06:10 +07:00
shakespeare.diy
a6a377f468 Add delete/dissolve for crews and Lab profiles via NIP-09 (v1.0.72)
- New useDeleteEntity hook: publishes a kind:5 deletion request referencing
  the target addressable event by `a` coord, `e` id, and `k` kind. Owner-only.
- New reusable DeleteEntityDialog: owner-gated trigger + confirmation with a
  clear "deletion is best-effort on decentralized relays" warning, redirect,
  and query invalidation so the item drops out of lists.
- Wired "Disband" into CrewDetailPage (founder only, redirects to /crews).
- Wired a "Manage → Delete Profile" card into CoachDetailPage for all Lab
  profile types (coach/filmer/editor/photographer/builder), redirects to /lab.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 20:02:33 +07:00
shakespeare.diy
2a55d1b3df Crew Agent Phases 0–2: opt-in AI guide (🐝 Sasquatch) per crew
Phase 0 — identity (lib/crewAgent.ts):
- Deterministic per-crew agent keypair derived from the crew coordinate
  (sha256 of a public salt + coord). Documented security tradeoff: the key
  is reproducible by design and holds no funds — used only for the agent to
  speak for itself. Includes kind-0 profile builder (bot:true, NIP-24) and
  a signAsCrewAgent helper for Phase 3.

Phase 1 — opt-in toggle:
- SkateCrew.agentEnabled parsed from a queryable ["t","crew-agent"] marker.
- "Crew Agent" toggle (Beta) in CrewForm; publishes the marker when on.
- NIP.md documents the crew-agent tag and the agent identity derivation.

Phase 2 — crew-scoped Sasquatch with receipts:
- lib/sasquatchBrain.ts: shared AI caller (model fallback) + tool-call parser.
- useCrewAgentChat: reuses the CVM data tools, seeded with crew context
  (name/location) so "near us" resolves to the crew's turf. Ephemeral/local,
  no keys, no publishing yet.
- CrewAgentPanel: chat dialog rendering agent answers PLUS structured receipt
  cards (spots / events / Lab pros / gear / crews) linking into the app.
- CrewDetailPage: "Ask Sasquatch" card + panel, shown only when the crew
  enabled the agent.

Also: Lab category tabs → fixed 5-across grid (no horizontal scroll). Bump 1.0.68.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 19:26:19 +07:00
shakespeare.diy
9a8609354e Polish native-only wallet card + add Crew↔Lab scene focus
Wallet (Request A): redesign the browser CashuWalletBrowserNotice into a
premium, intentional card — gradient hero header, "App only" badge, and a
"works in your browser now" panel highlighting NWC / Alby / Lightning
address zaps. Keeps the native-only gate; no bug reintroduced.

Crew ↔ Lab interconnection: crews can now declare an optional Lab scene
focus that surfaces them inside the matching Lab category, while regular
crews stay "Mixed" and only appear on the Crews page.
- types: CrewFocus ('mixed'|'builder'|'photo'|'coaching'), labels,
  crew-focus-* t-tag scheme, serviceType→focus mapping; parseSkateCrew
  extracts focus and excludes focus tags from vibes; SkateCrew.focus field.
- CrewForm: "Crew Type" selector (Mixed / DIY & Builders / Photo & Film /
  Coaching); publishes a queryable crew-focus-* t-tag when not mixed.
- useCrews: new useCrewsByFocus hook (relay-level #t filtering).
- CrewsPage: focus badge on cards + focus filter chips.
- LabPage: "Crews in this scene" horizontal strip beneath results, mapping
  building→builder, filming/photography/editing→photo, coaching→coaching.
- NIP.md: document the crew-focus-<scene> tag.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 18:08:51 +07:00
shakespeare.diy
18f32a14bf Gate built-in Cashu mint wallet to native-only (browser CORS reality)
Browsers block most Cashu mints (no CORS headers), so minting/melting fails unreliably in-browser — and the failed fetches spam the console. Rather than ship a broken browser feature:

- CashuWalletCard: in a browser, render a clear "built-in ecash runs in the app" notice pointing users to zaps (NWC/WebLN/Lightning address) which work fine in-browser; full mint wallet renders only on native (Capacitor)
- useCashu: skip coco/IndexedDB/mint init entirely in the browser (isNativePlatform guard) — stops the recurring "Failed to fetch mint" console errors at the source
- Zaps/tips (the app's real money flow) are unaffected — they never touch a Cashu mint

Native builds get the full wallet with minibits working directly (no CORS jail), per the earlier platform-aware work.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 17:32:40 +07:00
shakespeare.diy
fc7564a6f8 Platform-aware wallet mints (Option C: native + browser scoping)
- Add @capacitor/core + src/lib/platform.ts (isNativePlatform/getPlatform, web-safe)
- mintReachability: on native (no CORS jail) short-circuit probe to 'ok' so ANY mint works, including mint.minibits.cash
- useCashu default mint: native prefers minibits (matches skatespots@minibits.cash brand identity) then coinos/21mint; browser uses CORS-friendly candidates
- MintPrivacyManager recommended list is platform-aware: native leads with Minibits (recommended), web leads with Coinos and flags Minibits as native-only
- CashuWallet: 'This mint can't be reached from your browser' banner for the browser CORS case (never shows on native), pointing users to a web-friendly mint or the native app
- Lightning invoice error mentions the native-app option

Same codebase now uses minibits directly when running as the native app, and a CORS-safe mint when running in a web browser.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 17:22:41 +07:00
shakespeare.diy
b830bf965d Auto-heal wallets stuck on a CORS-blocked mint
- useCashu now probes existing mints on every init (not just when empty); if none are browser-reachable it adds a reachable candidate mint. This fixes existing users whose only saved mint is the legacy CORS-blocked mint.minibits.cash default.
- CashuWallet active-mint selection now prefers the first browser-reachable mint instead of blindly mints[0], so users aren't stuck with a mint that fails every invoice.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 17:11:31 +07:00
shakespeare.diy
71d3d60d09 Harden wallet mints: browser-side CORS reachability probe
- Add mintReachability.ts: probe a mint's /v1/info from the user's browser to detect CORS-blocked ('cors-blocked') vs down ('unreachable') vs 'ok'
- useMintManager.handleAddMint: probe before adding; reject CORS-blocked/unreachable mints with a clear message instead of letting them fail later on invoice
- useCashu default mint: instead of hardcoding one (unverifiable at build time), probe an ordered list of web-friendly candidates and add the first that's reachable in-browser; minibits excluded (mobile-app only)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 17:04:43 +07:00
shakespeare.diy
f5483a0dbc Add crew admin moderation + invite actions
- useCrewModeration: fetch NIP-32 `hidden` labels authored by founder+admins for a crew; useHideCrewPost publishes a kind-1985 hidden label (L/l org.skatespots.moderation, e=post, a=crew)
- CrewDetailPage feed: moderators can hide posts via an EyeOff button + confirm dialog; hidden posts filtered from the feed for everyone, shown greyed/"Hidden from feed" to moderators (reversible/auditable)
- CrewInviteDialog: founder+admins share an invite link/message (native share + copy); founder gets a shortcut to add a member key in crew settings
- Gate all admin actions on useCrewRole().canManage; moderators = founder + delegated admins
- Add MODERATION_LABEL_NAMESPACE/HIDDEN constants
- NIP.md: document feed moderation label format (author must be founder/admin) + invite semantics

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 17:01:28 +07:00
shakespeare.diy
0078f75f94 Fix wallet invoice failure (CORS-blocked mint) + history crash
- Root cause: mint.minibits.cash sends no Access-Control-Allow-Origin header, so browsers block it (NetworkError → "Failed to create invoice / Failed to fetch mint"). Default to CORS-friendly mint.coinos.io instead.
- useLightningOperations: detect CORS/network failures on receive & send, show actionable "Can't reach this mint — switch mints" message instead of a dead end.
- MintPrivacyManager: reorder recommended mints by browser reachability; flag Minibits as mobile-app-only (not web-reachable).
- useHistoryManager: fix `mintQuoteRepository.getAll is not a function` crash by probing for whichever list method the installed coco-cashu version exposes (getAll/getAllQuotes/list/...), failing gracefully.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 16:53:40 +07:00
shakespeare.diy
40e4a27565 Add crew NIP-05 (any-domain, founder-verified) + founder/delegated-admin governance
- SkateCrew: add nip05 + admins fields; parse `admin` role marker on p tags (admins = subset of members)
- CrewForm: NIP-05 input + per-member "Make admin" toggle; write nip05 tag + admin-marked p tags (works on create & edit via shared form)
- useCrewNip05: verify crew handle on ANY domain against the founder pubkey (verified/mismatch/unreachable), CORS-proxy fallback + caching
- CrewNip05Badge: green ✓ / red ⚠ / muted handle states with tooltips
- CollectiveMembers: show Admin chips, sort admins first, admin count in summary
- useCrewRole: centralized founder/admin/member/none roles (canManage, canEditProfile) — founder owns identity, admins get delegated powers with their own keys; egalitarian shared-editing intentionally unsupported
- CrewDetailPage: NIP-05 badge by crew name, viewer role badges (admin/member)
- NIP.md: document crew nip05 verification, admin role marker, and governance/conflict-resolution model

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 16:46:20 +07:00
shakespeare.diy
e48a5e6bc5 Add duration tiers + layered NIP-57 zap verification, extend featured/Pro to tours
- Add 4 duration tiers (7d/30d/90d/365d) with per-day price discrimination to usePlatformFee
- PlatformFeePayment: tier selector UI + attempt NIP-57 zap request (a/p tags), graceful fallback to plain LNURL invoice
- Add getZapProviderPubkey + getZapInvoiceFromLightningAddress helpers
- Add useFeaturedVerification hook (verified/claimed/none) querying kind 9735 zap receipts
- ProBadge: optional entityCoord to show green Verified state on detail pages
- Wire entityCoord through all 5 Become* Lab pages + CoachDetailPage
- Add featuredTour fee type; FeaturedUpgrade generalized for events + tours
- CreateTourPage: featured tag on publish + FeaturedUpgrade UI
- TourCard/TourDetailPage: render ProBadge; sort featured tours first
- Document tiers + zap-receipt verification scheme in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 16:18:15 +07:00
shakespeare.diy
dcfa1fe1b2 Add Pro/Featured display + optional featured events (Option B)
Lab:
- useLabProfiles sorts featured (Pro) profiles first
- ProBadge shown on Lab cards (LabPage) + Lab detail page; featured cards
  get a subtle amber highlight

Events (same no-paywall model as Lab):
- New FeaturedEventUpgrade: every event posts for free; optional fee adds a
  featured badge + top placement (reuses existing featuredEvent fee)
- CreateEventPage: removed the paid-event fee gate; opt-in featured toggle,
  writes a `featured` tag when active
- useSkateEvents sorts featured events first; EventCard shows ProBadge
  (compact in list view, overlay on the image in full view)

Docs:
- NIP.md documents the `featured` tag (unix-seconds expiry, self-attested)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 15:39:41 +07:00
shakespeare.diy
5c0d4715d5 Rework Lab fee into optional "Pro" upgrade (Option B, no paywall)
Basic Lab profiles are now always free to publish. The 5,000 sat fee is
opt-in and unlocks a Pro badge + featured placement instead of gating
publication behind a rate.

- types.ts: `featuredUntil` field on all 5 Lab types + SkateEvent,
  parseFeaturedUntil() + isFeaturedActive() helpers, wired into parsers
- usePlatformFee: exposes featuredUntilSeconds for the `featured` tag
  (platform owners get a 1yr window)
- New ProBadge + LabProUpgrade (opt-in toggle → reveals PlatformFeePayment)
- All 5 Become pages: removed rate-based fee gate and wallet-required gate;
  publish is free; when Pro is active a `["featured", "<expiry>"]` tag is added

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 15:31:49 +07:00
shakespeare.diy
cf514a7c3f Add Photographers & Builders to community stats + restructure Lab section
- useGrowthStats now queries kind 35004 (photographers) and 35005 (builders),
  which were previously omitted; added to totals, monthly buckets, allContent,
  and the content-mix donut (folded into the "Lab" slice)
- Community Stats page reorganized into clear sub-sections:
  * "The Lab · Creators & Services" — Coaches, Filmers, Editors,
    Photographers, Builders, with a total badge
  * "Community" — Tours, Crews
  This scales the growing role list cleanly instead of cramming one row.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 15:06:18 +07:00
shakespeare.diy
3b25e7bcf6 Add trust filter to Spot Chat + include crews in community stats
Spot Chat:
- Compact TrustFilter dropdown in the chat header (All / Verified / Trusted / Following)
- Filters messages by author trust via useBatchTrustScores; own messages
  always stay visible; shows a "N hidden by your trust filter" indicator

Community stats:
- useGrowthStats now queries kind 35006 crews (was missing entirely)
- totalCrews added to totals + monthly buckets + content-mix donut
- New "Crews" StatCard on the Community Stats page

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 14:58:10 +07:00
shakespeare.diy
ba3921b15c Add Spot Chat: ephemeral geohash-anchored local chat channels
Inspired by BitChat's location-channel concept, reframed for skaters and
kept fully SkateSpots-native (own kind + tag conventions, no interop).

- New ephemeral kind 24899 tagged with a geohash `g` (documented in NIP.md)
- useSpotChat / useSendSpotChat hooks (polled, community-relay aware)
- SpotChat component: live message list, composer, author profiles,
  Block/Neighborhood/City precision toggle, and light IRC-style flavor
  (/me action messages, /who participant count)
- Spot detail pages now show a per-spot block-level channel
- Map gains a "Local Chat" sheet anchored to the map center with the
  precision selector

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 14:52:48 +07:00
shakespeare.diy
dec2e730d2 CVM find_crews: Sasquatch can now live-search crews
- useCVMClient: add findCrews (queries kind 35006 across data relays, filters by
  city/country/vibe/search, returns CrewResult with member_count, vibes, url);
  add crews to getCommunityStats + CrewResult type
- SasquatchGuide: register find_crews tool in system prompt + switch handler,
  add crew keywords to needsLiveData, update app-facts (crews are searchable +
  mention cross-linking and Zap-the-Crew)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 14:35:16 +07:00
shakespeare.diy
f881c6513f Zap a whole crew: split a zap across confirmed members
- useCrewZap hook: splits a total evenly across recipients, resolves each
  member's Lightning address (batched kind-0 query), fetches an invoice per
  recipient and auto-pays via NWC → WebLN; per-recipient status
  (resolving/paying/success/skipped/failed) with manual-invoice fallback
- splitAmount util distributes remainder fairly (no lost sats)
- ZapCrewDialog: amount presets + custom, per-head preview, live progress rows,
  copy-invoice fallback when no wallet; excludes the current user
- CrewDetailPage: "Zap the Crew" button (recipients = founder + confirmed
  members via useCollectiveMembers); shown only when >1 recipient

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 14:31:45 +07:00
shakespeare.diy
d8d1ba32c9 Nutzap safety gate + crew↔Lab cross-linking
Nutzap:
- Gate sending behind NUTZAP_SENDING_ENABLED=false with clear docs; sending
  would require P2PK-locking proofs (fund-loss risk if wrong), so it's disabled
  until implementable/auditable. Receiving + reading unchanged. No dead-end
  button exists (hook was never wired to UI).

Cross-linking crews ↔ Lab collectives:
- types: parseAddrCoord/parseCrewRef/parseLabRefs helpers + LAB_KINDS; add
  crewRef to all 5 Lab interfaces/parsers and labRefs[] to SkateCrew
- CrewLinkField (pick one of your own crews) added to all 5 Become-* forms,
  shown only for collectives; publishes an a-tag on the Lab profile
- LabLinkField (pick your own Lab profiles) added to CrewForm; publishes a-tags
- CrossLinkCards: LinkedCrewCard on Lab detail ("Part of a Crew"), LinkedLabCard
  on crew detail ("Hire This Crew"), resolving coordinates via useCrew/useLabProfile

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 14:27:51 +07:00
shakespeare.diy
ed27f719fa Sasquatch: teach him all the new features + fix zap comment
- Expand AI system prompt with full feature map: Lab (coaches/filmers/
  photographers/editors/builders + individual vs collective), Crews (map-anchored,
  how they differ from the Lab), Stash, Tours, Shops, Gear Swap, Rider Card
- Document money layer interconnections: wallet rails (Cashu/NWC/WebLN), zap
  fallback order, nutzaps (NIP-61, receiving), CLINK/noffer, Geyser supporters
- Instruct Sasquatch that Crews/Stash/Wallet/Tours/Shops have no live search tool
  and must be answered from knowledge (prevents bogus tool calls)
- Add ~15 new STATIC_FAQ offline answers (crews, stash, builder, nutzap, clink,
  noffer, tours, shops, sell gear, supporters)
- Add Crews page guide context + crew jokes; refresh Lab suggestions
- Fix leftover "Zapped with Engorile!" default comment → SkateSpots branding

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 14:12:30 +07:00
shakespeare.diy
f10aa7971c Step 3: Crews — map-anchored collective feature (kind 35006)
- New SkateCrew kind 35006 + type/parser + useCrews/useCrew hooks
- /crews directory, /create-crew, /edit-crew, /crew/:pubkey/:identifier pages
- Reuses the collective engine: NIP-32 membership attestation (confirmed/pending),
  CollectiveMembers + ConfirmMembershipCard, ClinkPayButton, SpotPicker
- Activity v1: home spots + crew member feed (no events, per plan)
- Map: teal/cyan Crews layer at home turf with crew markers + popups;
  wired through SpotMap props, layer toggles (mobile popover + filters sheet),
  and mobileLayerCount badge
- Nav: Crews added to navItemKeys (desktop header, mobile bottom bar, sidebar)
  with en/es i18n keys
- NIP.md: documents kind 35006, tag taxonomy, and shared membership mechanism

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 13:59:50 +07:00
shakespeare.diy
657c36d264 Add Builder category to The Lab (kind 35005), collective-aware + spot-linked
Step 2 of the collective plan:
- types.ts: SKATE_BUILDER_KIND 35005, BUILDER_SERVICE/SKILL/SCALE label maps,
  SkateBuilder interface + parseSkateBuilder (entity/members/noffer/spotRefs +
  service_area & insured fields)
- useLabProfiles/useLabProfile: query + parse builders (profileType 'builder')
- BecomeBuilderPage (/become-builder): amber accent, build-type/materials/scale
  chip selectors, "how we work" gear, service area, licensed/insured switch,
  entity toggle (defaults to collective), noffer, and "spots you've built" picker
- LabPage: 'building' ServiceType — amber accent, Builders tab (5 tabs now,
  horizontally scrollable on mobile), OfferFAB item, filter sections, card
  type-icon/labels, empty state, counts & filtering
- CoachDetailPage: builder accent/icon/kind, "What We Build" + "How We Work"
  sections, service-area & insured cards, portfolio/social handling
- NIP.md: document Builder kind 35005 with tag taxonomy + collective example

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 13:33:52 +07:00
shakespeare.diy
9cb8d1181d Add individual/collective entity type + NIP-32 membership attestation to The Lab
Step 1 of the collective plan (shared primitive on existing Lab categories):
- types.ts: EntityType + entity/members fields on coach/filmer/editor/
  photographer interfaces & parsers; membership label namespace constants +
  parseMemberPubkeys / parseEntityType helpers
- useCollectiveMembers: cross-references claimed `p` tags against members'
  own NIP-32 (kind 1985) attestations -> confirmed / pending status
- useConfirmMembership: publishes a member's self-signed membership label
- EntityTypeField: individual/collective toggle + member npub picker (per-
  category accent) wired into all 4 Become-* forms
- CollectiveMembers: sidebar card with confirmed/pending trust chips
- ConfirmMembershipCard: one-tap confirm shown only to listed pending members
- CoachDetailPage: collective badge, members card, confirm-membership prompt
- Lab cards: compact collective badge with member count
- NIP.md: document entity tag + collective membership attestation flow

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 13:18:18 +07:00
shakespeare.diy
feddbaf5ef Add optional CLINK (noffer) self-custodial payments to Lab profiles + shops
Phase 1 CLINK integration (recognize & display only):
- types.ts: optional noffer field + isValidNoffer guard on SkateCoach,
  SkateFilmer, SkateEditor, SkatePhotographer, SkateShop; parsers read it
- NofferField: reusable opt-in input with live validation, added to all 4
  Become-* Lab forms and both Create/Edit shop forms
- ClinkPayButton: secondary "Pay via CLINK" action (QR + copy + nostr: deep
  link), rendered on CoachDetailPage and ShopDetailPage only when a valid
  noffer is present; never replaces the existing zap / Lightning Address flow
- useQRCodeScanner: classify scanned noffer1/ndebit1 CLINK codes
- NIP.md: document the noffer tag on Lab kinds + shops and a CLINK section

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 12:34:50 +07:00
shakespeare.diy
adeab62aa4 Add Photographer card to The Lab (kind 35004) + improve Filmer section
New Photographers category (blue-indigo) — separate from Filmers:
- New addressable kind 35004 + SkatePhotographer type + parseSkatePhotographer.
- Rich photographer taxonomy as queryable `t` tags: services (action,
  sequence, editorial, product, events…), camera bodies, lenses, lighting
  (Profoto/Elinchrom/Godox/triggers…), support/rig, and shooting style,
  plus a freeform "gear" tag and a portfolio link.
- BecomePhotographerPage form with chip-grid selectors for all gear
  categories + "what's in the bag" textarea, wired to /become-photographer.
- The Lab: 4th "Photo" tab (indigo accent) with its own filters, cards,
  empty state; OfferFAB gains "Offer Photography"; useLabProfiles fetches
  35004; CoachDetailPage renders photographers (and editors) properly with
  per-type accent, gear + portfolio display.
- NIP.md: documented the whole Lab (kinds 35001-35004) incl. photographer.

Filmer improvements (now video-focused, photography split out):
- Removed the token "Action Photography" service; added follow/line filming.
- Richer equipment list: cinema camera, HVX, action cam, gimbal, 4K/HFR.
- Filmers gain a freeform "what's in the bag" gear field + a reel/portfolio
  link, both surfaced on the detail page.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 11:27:22 +07:00
shakespeare.diy
d107c0a860 Fix Sasquatch auto-popup on / and /lab; move map attribution above mobile nav
- Sasquatch panel no longer auto-opens on the map (/) or The Lab (/lab).
  Added an `autoShow` flag to GuideMessage; only opt-in messages (onboarding)
  auto-open. The bubble stays minimized until tapped, and context messages
  still populate the panel when opened manually.
- Map CARTO/OSM attribution now sits above the mobile bottom nav (safe-area
  aware) instead of overlapping the Shops & Spots buttons; made it smaller
  and more subtle. Desktop keeps it in the bottom-left corner.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 10:09:15 +07:00
shakespeare.diy
a74225fe0d feat: platform monetization — listing fees for paid events & Lab profiles v1.0.62
Architecture:
- usePlatformFee hook: central fee config (featuredEvent/labProfile/featuredShop)
  5,000 sats each, 30-day validity, localStorage persistence, owner bypass
- PlatformFeePayment component: generic reusable payment gate
  auto-pay via NWC/WebLN, QR fallback, active/expired state display
  all fees route to skatespots@minibits.cash

Events (CreateEventPage):
- New optional "Entry Fee" field
- Triggers 5,000 sat platform fee when filled
- Stores entry-fee + paid tags on published event
- Free events: zero friction unchanged

The Lab (Become{Coach,Filmer,Editor}Page):
- Triggers 5,000 sat fee when "Rate" field is filled
- Submit disabled with clear CTA until fee paid
- Free/volunteer profiles: zero friction unchanged

Rule: "pay only when you earn"
- No rate set → publish instantly, no fee
- Rate set → 5,000 sats to SkateSpots, valid 30 days
- Platform owners always bypass

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 02:19:45 +07:00
shakespeare.diy
eba7542097 fix: FAB buttons above nav + Sasquatch no auto-trigger on map
- FABs (add spot/shop): switch from fixed bottom-[88px] to
  calc(3.5rem + env(safe-area-inset-bottom, 0px) + 0.75rem) so they
  respect device safe-area and always clear the bottom nav on all
  Android/iOS Capacitor builds
- Right-side filter pills: same fix, switched from absolute to fixed
  with the same safe-area-aware bottom value
- useSasquatchGuide: /map page now returns undefined message, meaning
  Sasquatch never auto-opens when navigating to Shops & Spots — user
  must tap the floating bubble to open it manually

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 00:12:53 +07:00
shakespeare.diy
cf1a18e941 Fix NUser constructor call: use positional args not object
NUser takes (method, pubkey, signer) as separate args.
Was passing {pubkey, signer} as first arg so pubkey was always undefined.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-09 15:53:21 +07:00
shakespeare.diy
52bf618b51 Fix amber login type: use x-amber instead of amber
NLoginOther requires type x-${string} to be a valid NLoginType.
Using bare 'amber' meant pubkey was never properly stored/rehydrated
from localStorage — it was always undefined/empty.

Using NLogin('x-amber', pubkey, ...) correctly stores pubkey through
the NLoginBase interface and survives localStorage serialization.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-09 14:57:20 +07:00
shakespeare.diy
0b1bf05e74 Fix useLoggedInAccounts: safely extract pubkey from amber login type
NLoginType doesn't expose pubkey field directly — amber login is cast
as unknown as NLoginType so l.pubkey was undefined. Use getPubkey()
helper that casts to extract pubkey safely for all login types.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-09 14:06:01 +07:00
shakespeare.diy
a6c220c911 Increase useAuthor timeout and retries for Capacitor WebView
WebSocket connections take longer to establish on first load in
Capacitor. Increase timeout from 8s to 15s and retries from 3 to 5.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-09 13:34:18 +07:00
shakespeare.diy
8256f05ca8 Wire up nostr-signer-capacitor-plugin for Amber login and signing
- Add CapacitorAmberSigner: a full NostrSigner implementation backed by
  the native nostr-signer-capacitor-plugin (login, signEvent, nip04,
  nip44 all go through Amber via native Android intents)
- LoginDialog: on Capacitor, call getAmberPublicKeyViaPlugin() directly
  instead of the intent:// URL redirect — returns a Promise, no
  callback URL or polling needed. Shows clear error if Amber not installed.
- useCurrentUser: on Capacitor amber logins use CapacitorAmberSigner
  so every publish/sign goes through the plugin, not window.location.href
- Web/PWA path unchanged — still uses URL redirect + AmberSigner

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-09 11:38:23 +07:00
shakespeare.diy
4f7852fec7 Fix Amber login: replace nostrconnect/relay flow with proper NIP-55 intent flow
The entire nostrconnect relay approach was wrong for a native Android/Capacitor app.
Amber on Android uses NIP-55 intents (nostrsigner: URI scheme), not NIP-46 relays.

New flow (same as Amethyst):
1. openAmberForLogin() fires nostrsigner:?type=get_public_key&callbackUrl=...
2. Amber opens, user taps approve
3. Amber calls back to org.skatespots.app://amber-callback?event=<pubkey>
4. AmberCallbackPage writes pubkey to localStorage('amber_pending_pubkey')
5. LoginDialog polls localStorage every 500ms and calls login.amber(pubkey)
6. Done — no relays, no waiting, no spinning

Also added login.amber(pubkey) method to useLoginActions.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 13:24:46 +07:00
shakespeare.diy
61795db997 fix: implement NIP-46 nostrconnect:// Amber login properly
- Fix useLoginActions.ts: remove generateNostrConnectParams, generateNostrConnectURI,
  NostrConnectParams, NostrConnectStatus which don't exist in @nostrify/react@0.2.17/login
- Rewrite LoginDialog.tsx Amber tab with proper NIP-46 client-initiated flow:
  - Generates ephemeral client keypair + random secret
  - Builds nostrconnect:// URI (no nostrsigner: scheme, no intents)
  - Opens Amber via window.open(_blank) - standard URI scheme Amber handles natively
  - Listens on relay pool for Amber's kind 24133 connect response
  - Decrypts with NIP-04, verifies secret to prevent spoofing
  - Uses NConnectSigner to call get_public_key
  - Creates NLogin bunker object and stores it persistently
- Bunker tab: direct NLogin.fromBunker() paste flow
- Extension tab: NIP-07 browser extension
- No clipboard hacks, no nostrsigner: scheme, no Android intent issues

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-19 00:02:21 +07:00
shakespeare.diy
916f067238 fix: rewrite login to use NIP-46 nostrconnect flow (Ditto pattern)
Completely replaces the broken NIP-55 intent approach with NIP-46:

- Amber tab: generates nostrconnect:// URI, opens Amber, waits for
  Amber to connect back via relay using NLogin.fromNostrConnect()
  (same pattern as Ditto/soapbox-pub)
- Bunker tab: paste bunker:// URI from Amber or nsec.app
- Extension tab: NIP-07 browser extension (desktop)
- useLoginActions: updated to match Ditto's implementation with
  nostrconnect(), generateNostrConnectParams, generateNostrConnectURI
- Clean mobile-first UI — no clipboard hacks, no intent tricks

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 23:49:55 +07:00
shakespeare.diy
72cb690e64 fix: increase relay query timeouts and fix AppConfigSchema for cold mobile loads
- Raise QUERY_TIMEOUT in useSkateSpots from 8s to 15s
- Raise AbortSignal.timeout in useLabProfiles and useSkateEvents from 10s to 15s
- Add useSkateRelay to AppConfigSchema so it's preserved when reading from localStorage

On a fresh phone (no warm WebSocket, no TanStack Query cache), the app
needs to: load JS bundle, boot React providers, open a WebSocket to
goskatespots.nostr1.com, and receive EOSE — all before the timeout.
On slow mobile connections this easily exceeded the old 8-10s limits,
causing /home to show empty sections while /map worked (the connection
was already warm by then). 15s gives cold connections enough headroom.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 15:28:26 +07:00
shakespeare.diy
f19851da92 security: implement all 8 protection measures
## 1. sanitizeUrl() (already existed, verified applied)
- Applied to ShopDetailPage, CoachDetailPage with urlDomain() tooltip hints
- Blocks javascript:, data:, vbscript: and all non-http(s) schemes
- Returns null for invalid URLs — link simply doesn't render

## 2. Geohash validation in parsers
- Added isValidGeohash() in types.ts using ngeohash.decode()
- Min 4 chars (country-level precision rejected), max 12 chars
- Rejects lat/lng outside Earth bounds
- Rejects 0,0 "null island" — classic bot artifact
- VPN/Tor unaffected: geohash comes from map pin, not user IP

## 3. Content length limits in all parsers
- MAX_TITLE_LEN=100, MAX_DESCRIPTION_LEN=3000, MAX_LOCATION_LEN=200
- MIN_TITLE_LEN=2, MIN_GEOHASH_LEN=4
- Applied to parseSkateSpot, parseSkateShop, parseSkateCoach,
  parseSkateFilmer, parseSkateEditor, parseSkateEvent

## 4. Smart WoT default (already in codebase, verified correct)
- Logged out / <5 follows → 'all' (don't punish new/anon users)
- Logged in with ≥5 follows → 'trusted' (sort trusted first)
- Soft mode: trusted content floats to top, nothing hidden
- Active in MapPage and EventsPage

## 5. Trusted count stats in useGrowthStats
- Added trustedSpots and trustedMappers to GrowthStats type
- Heuristic: pubkeys with ≥2 published items = organic community member
- CommunityStatsPage now shows "🛡 X from active accounts" below spot count
- Purely additive — both numbers always visible

## 6. OfficialBadge component (already existed, wired up)
- Added to SpotCard, EventCard, ShopDetailPage, CoachDetailPage
- Only renders for FOUNDER_PUBKEY (20e7c953...)
- Zero-friction: invisible for all other pubkeys
- Impersonation attempts immediately visible

## 7. Leaflet marker clustering
- Installed leaflet.markercluster v1.5.3 + @types
- MarkerClusterGroup replaces direct marker.addTo(map)
- Custom cluster icon: red/orange gradient circle with count
- maxClusterRadius: 60px, spiderfyOnMaxZoom: true
- MarkerCluster CSS loaded from CDN in index.html

## 8. DM phishing warning banner
- PhishingWarningBanner component in DirectMessagesPage
- One-time dismissible (localStorage key: dm-phishing-warning-dismissed)
- Message: SkateSpots will never DM you asking for keys/payments
- Uses useLocalStorage hook, Shield icon, X dismiss button

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 14:03:15 +07:00
shakespeare.diy
0451887a17 feat: extended community stats page with Nostr growth data
New useGrowthStats hook:
- Queries wss://goskatespots.nostr1.com directly (authoritative source)
- Fetches all 7 content types in parallel: spots, shops, events,
  coaches, filmers, editors, tours
- Deduplicates addressable events (keep newest per pubkey+kind+d)
- Buckets events by month with gap-filling for continuous X axis
- Computes cumulative spots line chart data
- Extracts top 10 mappers by spot count
- 15 min stale time — stats don't need to be live

New useGeyserStats hook:
- Queries Geyser GraphQL for supporter count + total sats raised
- Reuses same API already used in SupportersPage

Rewritten CommunityStatsPage:
- Section 1: 8 hero stat cards (spots, shops, events, coaches,
  filmers, editors, tours, mappers) with per-type accent colours
- Section 2: 4 charts (cumulative spots area, monthly stacked bar,
  active mappers bar, content mix donut)
- Section 3: Top 10 mappers leaderboard with avatars + 🥇🥈🥉
- Section 4: Supporters banner with live Geyser count + sats raised
- Footer: data source + verifiability note
- Full skeleton loading for each section independently
- Error state for relay connectivity issues

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 12:54:32 +07:00
shakespeare.diy
bd3e4bee4e perf: lazy loading, faster timeouts, better caching, chunk splitting
- AppRouter: lazy load all 40+ pages (only Index is eager)
  → initial JS bundle no longer includes code for pages never visited

- MapPage: lazy load SpotMap/Leaflet (~150KB) behind Suspense
  → Leaflet only downloads when user navigates to /map

- useSkateSpots/useShops/useMarketplace/useTours:
  → timeout: 30s → 8s (fail fast, show error sooner)
  → staleTime: 30s → 5min (trust cache, fewer relay round-trips)
  → gcTime: 5min → 30min (keep data in memory longer)
  → refetchOnMount: 'always' → true (use cache when data is fresh)

- Remove console.log/warn/debug spam from production hooks

- vite.config: manual chunk splitting for leaflet, nostr-tools, bitcoin
  libs, radix-ui, react-router — each chunk cached independently
  by browser, only re-downloaded when that library updates

- vite.config: esbuild drops console.log/warn/debug in prod build

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 11:32:24 +07:00
shakespeare.diy
6e46214747 Fix profile not loading after Amber login - rewrite NostrSync
Root causes found:
1. syncedUsers blocked re-sync permanently even on failure — first attempt
   fires before WebSocket connections are ready on mobile, fails silently,
   gets marked done, profile stays anonymous forever that session
2. useAuthor had 5min staleTime — empty {} result from pre-login query
   was cached and not refetched after login
3. NostrSync did not force-remove the stale useAuthor cache entry

Fixes:
- Rewrite NostrSync with proper retry logic (up to 5 attempts, backoff)
  - Tracks success/failure per pubkey, only stops on confirmed success
  - Re-triggers on user.pubkey change (catches Amber polling completing)
  - Forces queryClient.removeQueries on author cache before each attempt
  - Tries Citrine → bootstrap relays → main pool in order
- useAuthor: reduce staleTime 5min→1min, expand fallback relay list
- Both files now use same BOOTSTRAP_RELAYS set for consistency

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-17 23:35:18 +07:00
shakespeare.diy
3a4e4ead05 fix: rewrite useCVMClient to query relays directly instead of NIP-44 round-trip
The encrypted CVM request/response cycle (kind 21000/21001) was too unreliable
in the browser — timeouts on every call. Replace with direct Nostr relay queries
using nostr.group() which is already proven to work in the app. Same filtering
logic as the server. No dependency on CVM server being up for data queries.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-17 12:32:59 +07:00
shakespeare.diy
a9c677b58d fix: rewrite useCVMClient to use Relay.connect() instead of SimplePool
nostr-tools v2 SimplePool.subscribeMany() callbacks (onevent/oneose) no
longer fire reliably in browser context, causing the CVM client to always
time out. Switch to raw Relay.connect() which uses the v2 API correctly.
Race across all CVM relays and resolve on first valid response.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-17 12:10:31 +07:00
shakespeare.diy
252163625e feat: rebuild Sasquatch as proper CVM agent bridge
- Rich system prompt that explains the CVM architecture: it aggregates
  across multiple relays, deduplicates, and filters by features inside
  events (complex queries that plain Nostr filters can't do)
- Switched tool call format from fragile regex (TOOL:name {...}) to
  clean JSON {"tool":"name","args":{...}} that's easier for the LLM
- More robust JSON parsing — scans the full AI response for the tool call
- Better second-pass prompt: AI formats results with bullet points + links
- Updated suggested questions to showcase real CVM queries
- CVM failure gives a clear message explaining what the server does

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 22:39:51 +07:00
shakespeare.diy
966907a848 chore: clean up console noise - remove debug logs from NostrProvider, NostrSync, useShakespeare
Removed:
- [NostrProvider] Routing query to read relays (fired on EVERY query)
- [NostrProvider] Publishing kind X to relays
- [NostrSync] Starting sync / querying profile / profile query returned / sync completed
- [NostrSync] Profile not found on main relays
- [NostrSync] Profile synced and cached
- [Shakespeare] Fetching / Model / Response status debug logs

Kept all warn/error logs for actual failure cases.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 22:23:00 +07:00
shakespeare.diy
d18b62431e fix: remove broken CORS proxy, call ai.shakespeare.diy directly
proxy.shakespeare.diy was returning NetworkError on all requests.
Direct calls to ai.shakespeare.diy work fine from a real browser.
Kept debug logging to confirm correct URL and pubkey.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 22:01:15 +07:00
shakespeare.diy
b815cbfda6 fix: remove dead relay.nostr.band from all relay lists across the app
Removed from: useCVMClient, NostrSync, useLoggedInAccounts, useAuthor, SettingsPage
Replaced with relay.damus.io where a fallback was still needed.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 21:49:02 +07:00
shakespeare.diy
c643ddb105 fix: add relay.ditto.pub back to CVM client relay list
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 21:47:14 +07:00
shakespeare.diy
9b50cda191 fix: update CVM client relays - remove dead relay.contextvm.net and relay.ditto.pub
Use goskatespots.nostr1.com as primary (matches CVM server config),
plus relay.nostr.band and relay.damus.io as fallbacks.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 21:42:22 +07:00
shakespeare.diy
51751f9ea7 fix: route Shakespeare AI calls through CORS proxy for iframe compatibility
All fetch calls to ai.shakespeare.diy now go through proxy.shakespeare.diy
so they work from inside the iframe.diy preview sandbox.

The NIP-98 token is still signed against the original URL (not the proxy
URL) so the server-side signature validation passes correctly.

Affects: sendChatMessage, sendStreamingMessage, getAvailableModels

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 20:33:35 +07:00
shakespeare.diy
223b7602bf fix: correct model ID to 'shakespeare/claude-sonnet-4.6' for Shakespeare AI
Previous attempts used 'shakespeare' and 'claude-sonnet-4-5' — both invalid.
The correct format matches the recentlyUsedModels in ai.json config:
provider/model-name with dots not dashes.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 20:26:01 +07:00
shakespeare.diy
2aeb41afdc fix: use correct model ID 'claude-sonnet-4-5' for Shakespeare AI calls
The model name 'shakespeare' does not exist on the API — it was returning:
  "Model 'shakespeare' is not supported" (400 invalid_request_error)

Updated both sendChatMessage calls in SasquatchGuide and the default
model in useShakespeare hook (sendChatMessage + sendStreamingMessage).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 20:05:26 +07:00
shakespeare.diy
13eac70cec fix: improve 400 error logging to surface raw API body
Previously, the catch block around response.json() would swallow the
actual error body and replace it with a generic "Invalid request" message.

Now:
- Read response as text() first (never fails)
- Log the raw body to console as [Shakespeare API 400] raw body: <text>
- Parse JSON from the text string separately
- Fallback error message includes the raw body so it's visible in console

This will make it easy to diagnose exactly what the Shakespeare API
is rejecting (model name, message format, missing field, etc.)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 19:49:09 +07:00
shakespeare.diy
054c06d573 fix: shorten system prompt, improve 400 error logging in Shakespeare hook
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 19:42:22 +07:00
shakespeare.diy
fadf36d86e feat: upgrade Sasquatch from static FAQ to live AI agent with CVM data
- Add useShakespeare.ts hook (Shakespeare AI via NIP-98 auth)
- Add useCVMClient.ts hook — calls SkateSpots CVM server tools over
  Nostr directly from the browser (NIP-44 encrypted request/response,
  no @contextvm/sdk bundle needed, pure nostr-tools)
- Rewrite SasquatchGuide to use Shakespeare AI + live CVM data:
  * Static FAQ answers for protocol/app questions (fast, no API)
  * Shakespeare AI for natural language understanding
  * AI detects TOOL: calls in its response and executes them live
  * CVM server queried for find_spots, find_events, find_coaches,
    find_gear, get_community_stats
  * Streaming "thinking..." and "Searching Nostr..." indicators
  * Live/offline status badge shows CVM server connectivity
  * Graceful fallback when CVM server unreachable
  * Login nudge for unauthenticated users

CVM server pubkey: e3564bb877adb0c99a7e4973ccb22e516d7d9eca0027d0bd62a04174ed670530
CVM server host: 185.18.221.2 (LNVPS, Dublin)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 19:30:00 +07:00
shakespeare.diy
ebbf2d6326 fix: remove orphaned canvas code from RiderCardPage causing ctx/Printer is not defined crash
The drawCardToCanvas function had an old version of the card rendering logic
that leaked outside the function body (lines 505-731 were orphaned code).
This code referenced local variables (ctx, p, data, qrX, qrSize, PAD, etc.)
that only existed inside the function, causing ReferenceError at module load time
and crashing the entire app with a blank screen.

Also added missing lucide-react imports (Printer, Loader2, Share2, Download,
Check, Copy, Award, Zap, Info, Palette, Shield, ChevronUp, ChevronDown) that
were used in the JSX but not imported, causing the secondary Printer is not
defined ReferenceError.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 17:02:59 +07:00
shakespeare.diy
838ef3f355 feat: SkateSpots Rider Cards — 3 card types, 6 themes, holographic shimmer, print-ready
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 14:29:54 +07:00
shakespeare.diy
9fc39651ca fix: remove MIT note from /license, fix Buy/Sell Gear button on /map, expand stash storage locations
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 11:21:45 +07:00
shakespeare.diy
4e227f0367 feat: License page, Video Editors in Lab, Buy/Sell Gear, Stash locations, Supporters page
1. /license page
   - New LicensePage with "YOU ARE FREE TO SKATEBOARD" license text
   - Copyright 2025-∞ with neon THUG2 styling
   - Footer: "License" and "Supporters" links added next to OpenSats

2. /lab — Video Editor category
   - New SKATE_EDITOR_KIND (35003) in types.ts
   - EDITOR_SERVICE_LABELS/DESCRIPTIONS (8 edit types)
   - EDITOR_SKILL_LABELS/DESCRIPTIONS (8 software/hardware skills)
   - SkateEditor interface + parseSkateEditor() parser
   - useLabProfiles updated to query kind 35003 + 'skateeditor' tag
   - LabPage: new "Video Editors" tab (purple, Scissors icon)
   - LabPage: "Offer Editing" button in header
   - BecomeEditorPage: full form with edit types, skills, hardware,
     software, portfolio, contact, location, wallet requirement
   - Route /become-editor registered in AppRouter

3. /map Quick Actions
   - "Sell Gear" → "Buy/Sell Gear" with ArrowRightLeft icon
   - Button now links to /marketplace (browse + sell)

4. /stash — Storage locations
   - StashItem.storageLocation field: home/warehouse/friends/parents/wardrobe/other
   - AddItemDialog: 6-button grid to tag where gear is stored
   - StashItemCard: shows location badge on each card
   - Filter bar: pill filter chips to filter stash by location
   - storageLocation saved/restored via onAdd handler

5. /supporters page
   - btcmap.org-style supporters page
   - Live Geyser GraphQL leaderboard (skatespots project)
   - Avatar grid with sat amounts, anon fallbacks
   - Mission section + "Send Sats" section (Lightning address + Geyser)
   - Added to footer and desktop "More" dropdown and mobile menu

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 11:05:03 +07:00
shakespeare.diy
122bdfb102 Sync with skate-spots: THUG2 neon theme, remove old AuthDialog and unused lib files
- Replace generic MKStack default CSS with THUG2-inspired neon theme:
  - Neon red primary (#ff0040) and neon lime accent (#ccff00)
  - Pure black dark mode background
  - Custom CSS variables for gradients, sidebar, and animations
  - Leaflet map dark mode overrides
  - Custom scrollbar styling
  - Mobile touch target improvements and iOS input zoom fix
  - Safe area insets for notched devices
  - scrollbar-hide utility class
  - Animation classes: neon-glow, stagger, fadeIn, slideUp, pulse-glow
  - Glass effect and gradient-text utilities
- Remove AuthDialog.tsx (old Sign Up flow replaced by newer LoginArea/SignupDialog)
- Remove appBlossom.ts and appRelays.ts (not used in this project)
- Remove useLoginActions.test.tsx (not present in reference version)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 23:00:01 +07:00
shakespeare.diy
540eeead2f fix: replace @noble/curves/secp256k1 dynamic import with nostr-tools finalizeEvent
@noble/curves v2 dropped the ./secp256k1 subpath export. The manual
getEventHash + schnorr.sign + bytesToHex pattern is replaced with
nostr-tools' finalizeEvent() which handles id computation and signing
internally, with no external subpath dependencies. Also removes the
now-unused getEventHash import and bytesToHex helper.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 22:28:45 +07:00
shakespeare.diy
0b4fc341a1 fix: replace @noble/hashes/utils import with inline bytesToHex helper
@noble/hashes v2 dropped the ./utils subpath export, breaking the import
in useDirectMessages.ts. Replaced with a simple inline implementation
that has zero new dependencies and works correctly with any build tool.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 22:24:56 +07:00
shakespeare.diy
e29d35f1e4 fix: restore full skate-spots source tree from misplaced src/src/ copy
The entire skate-spots codebase was accidentally copied into src/src/ and
public/public/ instead of src/ and public/ directly. This left the real
src/ directory as blank MKStack template stubs, explaining why the landing
page showed the default template instead of SkateSpots.

Changes made:
- Copied all files from src/src/ up to src/ (App.tsx, AppRouter.tsx, main.tsx,
  all pages, components, hooks, contexts, lib, i18n, test)
- Copied all files from public/public/ up to public/ (images, manifest, etc.)
- Deleted src/src/ and public/public/ nested directories
- Removed HelloWorld.tsx template stub from pages/
- Retained the previously fixed index.css (Tailwind v3 directives)

Full skate-spots feature set is now active:
- All 30+ routes (map, events, spots, coaches, marketplace, wallet, etc.)
- NWCProvider, LanguageProvider, PWAInstallPrompt, AmberCallbackHandler
- Skate community relays (goskatespots.nostr1.com, wotr.relatr.xyz, etc.)
- Outfit Variable font, Amber pre-check in main.tsx
- All hooks (useSkateSpots, useCoaches, useZaps, etc.)
- i18n support (en/es)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 22:18:28 +07:00
shakespeare.diy
06fae294b8 New project created with Shakespeare
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 19:49:07 +07:00