Commit graph

90 commits

Author SHA1 Message Date
shakespeare.diy
cbd30fee00 Add skate roles to the crew roster (filmer, photographer, etc.)
A skate team is more than "members" — the filmer, photographer and editor are
core functions. Crews can now assign optional, non-pretentious skate roles:

- Types: CrewRole (filmer/photographer/editor/artist/manager/og) + CREW_ROLE_META
  labels/emoji, `crew-role` tag parser, and `crewRoles` on SkateCrew. Roles are
  additive to membership and grant NO powers (only `admin` does); a member can
  hold roles AND be an admin. Only honoured for founder/listed members.
- CollectiveMembers: optional rolesByPubkey → renders 🎥 Filmer / 📷 Photographer
  flex chips next to each member.
- CrewLeadership: leader + captains show their skate-role chips.
- CrewForm: per-member role toggles + a founder ("Your roles") picker; publishes
  `crew-role` tags scoped to the roster.
- CrewDetailPage: builds rolesByPubkey and threads it to members + leadership.
- NIP.md: document the `crew-role` tag + example.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 00:07:30 +07:00
shakespeare.diy
91045e17ae Add "The Part" — crew video/montage (YouTube + Instagram)
A skate crew is defined by its footage. Crews can now feature a headline video:
- videoEmbed.ts: safe parser accepting ONLY YouTube and Instagram URLs (full
  parts on YT, clips on IG). Extracts video id / shortcode; rejects everything
  else so we never render an untrusted iframe. No native/self-hosted video.
- CrewVideoSection ("The Part"): cinematic section — YouTube click-to-load
  facade (poster + play button, no heavy iframe/cookies until play) and an
  Instagram permalink embed, framed with skate language.
- SkateCrew gains `videoUrl` (tag `video`), validated through the embed parser.
- CrewForm: "The Part" input with live source detection (YouTube/Instagram) and
  validation; publishes the `video` tag.
- Crews grid: crews with footage flex a "▶ Part" badge as a discovery signal.
- NIP.md: document the `video` tag (YouTube/Instagram only) + example.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 23:59:50 +07:00
shakespeare.diy
4f3339650d Add crew roles & relationships (Phase 3: sponsor + Cordn coordinator)
- Extend SkateCrew with `sponsorRef` (sponsoring shop, NIP-15 stall coord) and
  `coordinator` (Cordn/MLS encrypted-chat Delivery Service pointer), plus parser.
- CrewSponsorBanner: "Sponsored by [Shop]" banner that resolves and links the
  backing shop; renders nothing if the shop can't be resolved.
- CrewCoordinatorCard: surfaces the crew's private-chat coordinator (E2E · MLS),
  explains what the coordinator can/can't see, shows the operator, and prompts
  the leader to set one up when absent — the UI foundation for Cordn chat.
- Wire the sponsor banner (under hero) and coordinator card (sidebar) into
  CrewDetailPage.
- CrewForm: add "Sponsored by" and "Private Chat Coordinator" inputs with
  validation; publish `sponsor` and `coordinator` tags.
- NIP.md: document the `sponsor` and `coordinator` tags, the structured invite
  kind 36229 (with accept/decline flow), and the Cordn/MLS private-chat model
  and why it's the chosen fit for crews.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 23:52:33 +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
bb6245e53c Skater profile: browse real map spots for home spot + always use kind-0 profile pic
Home spot:
- Add "Browse spots" button opening a searchable dialog of real mapped spots (useSkateSpots)
- Picking a spot stores its title + an `a` (30818:pubkey:d) coordinate; shows a linked-spot chip with unlink
- Profile page renders a linked home spot as a link to /spot/:pubkey/:identifier
- Parse homeSpotRef from the `a` tag; document in NIP.md

Profile pic consistency:
- Remove the skate-specific image override/upload — the skater card now always
  uses the kind-0 profile picture so both sections show the identical photo
- "Profile Pic" section is read-only with a "Change profile photo" link to settings
- Drop the `image` tag from published skater events; note the change in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 21:44:12 +07:00
shakespeare.diy
4c9839eb45 Rename "Action shot" to "Profile Pic" and add custom Level option for skaters
- Become-Skater form: photo section relabeled "Profile Pic"
- Level picker now includes a "Custom" toggle with a free-text input (≤40 chars) for labels like "OG", "Weekend Warrior", "Retired"
- Store custom level as a `level_custom` tag (mutually exclusive with preset `level`)
- Render custom level everywhere presets show: profile (NIP19Page), SkatersPage cards, SkaterDirectoryCard
- Document `level_custom` tag in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 21:34:59 +07:00
shakespeare.diy
3ef11eda61 Refine Lab banner copy, stance to Regular/Goofy, full profile carry-over chips on Become-Skater, remove Back to Leaderboard from profile
- Lab "Just a skater?" banner: drop redundant "→ join the directory" trailer (arrow icon already present)
- Skater stances distinct emojis (🛹 Regular / 🤙 Goofy); already limited to Regular & Goofy
- Become-Skater: fix missing ShieldCheck/Zap imports; show ALL carried-over kind-0 fields (photo, name, bio, website, nip05, zaps) as read-only chips
- Profile (NIP19Page): remove hardcoded "Back to Leaderboard" link and unused ArrowLeft import

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 21:28:55 +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
11c67fcf22 Profile: add Lightning, Silent Payment, Sponsors & Cordn fields
Editable in EditProfileForm and displayed on the profile:
- Bitcoin Lightning Address (lud16) — now editable in-form, shown with a
  labeled Lightning row in the Info card.
- Bitcoin Silent Payment Address (BIP-352, custom `bitcoin_silent_payment`)
  — validated (sp1…), shown as a copyable, truncated row.
- Sponsors & Support (custom `sponsors`) — freeform highlight, rendered in
  a dedicated pink "Sponsors & Support" sidebar card.
- Cordn contact link (custom `cordn`) — private MLS messaging link
  (cordn.net), shown as a "Message privately" row in the Info card.

Crews and the CLINK noffer tip button were already present, so they are
left as-is. Added isValidSilentPayment / isValidLightningAddress
validators and documented the custom kind-0 fields in NIP.md.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 20:00:21 +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
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
6fb7314f6c Fix real Amber login hang: skip package-name so getPublicKey uses the intent, not the stalling ContentProvider (v1.1.9)
Root cause found in the plugin's native getPublicKey (NostrSignerPlugin.java):
when a packageName is set, it FIRST tries Amber's ContentProvider (background,
no-UI). On GrapheneOS that cross-app ContentResolver query is blocked and never
returns, so it never falls through to startActivityForResult(nostrsigner:) —
the branch that actually opens Amber's approval UI. Result: eternal
"Waiting for Amber".

Fix: getAmberPublicKeyViaPlugin no longer calls setPackageName and no longer
passes packageName; it passes only the get_public_key permission. With no
package name the plugin skips the content-resolver branch and launches the
intent directly, opening Amber's UI. Amber returns both pubkey and its package,
so later signing still works. Paired with the auto-injected <queries> manifest
block so the OS can resolve the intent in the first place.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 14:29:35 +07:00
shakespeare.diy
07699592da Fix Amber "Waiting…" hang on GrapheneOS: unblock setPackageName + auto-inject <queries> (v1.1.9)
Two concrete root causes for the eternal "Waiting for Amber…":

1. setPackageName could hang forever on GrapheneOS. getPlugin() awaited it with
   no timeout, so getPublicKey() (and its 90s timeout) was never even reached.
   Now setPackageName races a 3s timeout and continues regardless — the Amber
   package is passed explicitly on every call anyway, so it's non-essential.

2. AndroidManifest.xml had no <queries> block. On Android 11+/GrapheneOS the OS
   silently blocks the nostrsigner intent without it, so Amber never opens. Added
   scripts/inject-amber-queries.mjs (idempotent) and wired it into android:init /
   android:sync / android:build so the block is guaranteed present in every APK,
   surviving cap sync regeneration.

Bump version to 1.1.9.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 14:27: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
4ed1d9a803 Fix crew card layout, Amber web login hang, Stash encryption gate, and add signup welcome step
- CrewCard: rebuilt to fit any screen width — clip overflow, truncate name/location, single wrapping badge row, consistent avatar sizing; responsive header
- Amber web login: after opening Amber, drop user on manual paste step after 2.5s so the spinner never hangs when the callback opens in a new tab or clipboard is blocked; keep polling on the paste step so a late callback still auto-completes
- Stash: hasNip44/hasNip04 now fall back to the active login when pubkey matching fails, so logged-in nsec/amber users are no longer wrongly told "Encryption Required"
- Signup: added an explicit welcome step that explains account creation and only generates the keypair when the user taps "Create my account" (no more silent auto-creation on dialog open)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 13:27:24 +07:00
shakespeare.diy
a83611072a Fix Amber NIP-44 gate on Stash, mobile Crew detail layout, remove Quick Snap
- signerCapabilities: recognize x-amber login type as NIP-44/NIP-04
  capable (Amber implements nip44_encrypt/decrypt via native intents and
  URL redirects). Fixes "Encryption Required" wall on Stash for users
  logged in with Amber on Android.
- CrewDetailPage: restructure hero header so actions wrap below the
  title on mobile instead of squeezing it in a cramped 3-column flex;
  smaller avatar/title on small screens, truncating location.
- MapPage: remove Quick Snap from the mobile Add speed-dial and all its
  handlers, refs, web-fallback input, and now-unused imports.
- zapstore.yaml: explicit orange icon + metadata_sources: [] so the
  correct logo is always used on the Zapstore listing.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 12:31:34 +07:00
shakespeare.diy
8a6d84ca3d Fix map tiles (drop keyed CARTO for free OSM + CSS dark filter), restore corrupted parseSkateCoach in types.ts
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 09:56:47 +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
538ef67b0b Security: remove hardcoded OpenRouter key (C1) + escape weather HTML (H1)
Address the two genuinely exploitable findings from the OpenCode audit:

C1 (critical) — sasquatchBrain.ts shipped a hardcoded OpenRouter API key in
the client bundle, extractable and abusable by anyone. Removed it; OpenRouter
is now opt-in via VITE_OPENROUTER_API_KEY and skipped when unset. Sasquatch
still works for everyone via the existing keyless Pollinations fallback.
(The leaked key must also be rotated/revoked on openrouter.ai.)

H1 (high) — SpotMap popup interpolated external weather-API data directly into
innerHTML. Added an escapeHtml() helper and applied it to every weather/AQI/
forecast value so a compromised weather API can't inject HTML/JS.

Triage of the rest: C3 (crew agent derived key) is a documented, deliberate
tradeoff — agent holds no funds and only speaks for itself. H2 (chart.tsx
dangerouslySetInnerHTML) is the stock shadcn chart with app-controlled config,
not user input. H3/H4 (GitHub Actions) don't apply — no .github/ in this repo.
C2 (NWC in localStorage) is standard for web Nostr clients. npm audit fix +
key rotation are operator steps done outside the app.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-18 17:04:53 +07:00
shakespeare.diy
8b080fb7f1 Spot detail: make mapper clickable + add Message Mapper button
The "Tip the Mapper" card now links the avatar/name to the mapper's profile
(/{npub} via the NIP-19 router) and adds a "Message Mapper" button that opens
an encrypted DM (/messages/{npub}). Makes it easy to contact whoever added a
spot — e.g. to ask them to add photos to a photoless submission. The message
button is hidden when you're viewing your own spot.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-15 10:03:58 +07:00
shakespeare.diy
6e11aaab7d Crew form UX: expand Crew Type to 7 scenes (Art, Music, Wellness) mapping to new Lab crafts; rethink Crew Band with Lab-music interconnect nudge; surface member visibility to founders; add Artist/Musician open-call options; update Crews page filters + NIP.md
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-10 22:14:21 +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
8056b02ad1 Crew band gig booking, hide treasury, CLINK on profiles, friendlier CLINK, remove desktop messages icon
- Add "punk" + "rap/hip-hop" crew vibe presets
- Hide Crew Treasury behind SHOW_CREW_TREASURY flag (detail page + form), code kept
- Improve crew band section into a dedicated card with a "Book them for a gig"
  action that deep-links to a Concert event hosted by the crew
- CreateEventPage: prefill event type + title from ?type= / ?title= params
- CLINK (a): expose CLINK tip code on user profiles (EditProfileForm field +
  "Tip in Bitcoin" button on profile view, stored as custom kind 0 field)
- CLINK (c): relabel "Pay via CLINK" -> "Tip in Bitcoin" with a "what's this?"
  tooltip explaining it in plain language
- Remove standalone desktop Messages icon (kept in the More menu)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 20:29:44 +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
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
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
5d07524751 Glossary: drop difficulty levels, expand & correct tricks
- Removed difficulty categorization entirely (type, badges, styles) —
  cleaner and no arbitrary skill labels.
- Added many requested/missing tricks with proper research and correct
  alt-names, including:
  • Rotations category: FS/BS 180, FS/BS 360, half-cab, full cab
    (caballerial), fakie 180s.
  • Flip tricks: backside flip, frontside flip, FS/BS heelflip, double
    heelflip, 360 hardflip, 360 pressure flip.
  • Grinds: feeble grind now notes "Grosso grind" aka; added Willy/
    Barley, pole jam; inventor credits (Suski, Losi, etc.).
  • Fundamentals: nose ollie/north, one-foot, no-foot/nothing, nose
    manual, caveman, tic-tac.
  • Shoves: nollie shove, ghetto bird.
  • Grabs: tailbone, airwalk. Stance/slang: pop, line, kook, sticker
    slap, etc. Hardware: durometer.
- Updated the local glossary answerer index to cover all new aka names
  (e.g. "grosso grind", "half cab", "north ollie" now answer locally).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-29 23:08:37 +07:00
shakespeare.diy
cc84713fe4 Make Sasquatch resilient: local glossary answers + backup AI provider
Fixes the "brain's catching traffic" dead-end for trick/terminology
questions and reduces reliance on a single flaky free API.

- Local glossary answerer (answerFromGlossary): trick/how-to/terminology
  questions ("what is a feeble grind", "how do I do a kickflip", "grosso
  grind") are now answered instantly from the 150+ entry glossary with
  ZERO API calls — so they never hit a rate limit.
- Wired into SasquatchGuide as (a) a fast path before the AI and (b) a
  last-resort fallback when the AI is unreachable.
- sasquatchBrain now has multi-provider fallback: OpenRouter free models
  (with 429 backoff) → keyless Pollinations backup. Only throws if both
  fail, letting the caller fall back to local knowledge.
- Replaced the unhelpful fallback message with useful local links +
  a nudge to ask about a specific trick.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-29 23:04:41 +07:00
shakespeare.diy
309d012db1 Massively expand Trick Glossary (tricks + terminology + hardware)
Grew the glossary from ~80 to ~150 entries across 15 categories.

- Added many more tricks per the community list: impossible, gazelle
  flip, pressure/hospital/dolphin flips, bigger spin/flip, suski/willy/
  losi/hurricane grinds, japan/madonna/slob/seatbelt/roastbeef/benihana/
  lien/cannonball/christ grabs, 720/1080, casper/anti-casper, spacewalk,
  walk the dog, g-turn, and more — with inventor credits and aka names.
- New reference categories: "Transition & Vert Terms", "Stance &
  Movement", and "Hardware & Equipment" (deck, trucks, bushings,
  bearings, risers, wheel bite, etc.).
- Reference categories hide difficulty badges (added `reference` flag +
  `hideLevel` on TrickRow); search still spans everything.
- Updated hero copy + SEO description to reflect the broader scope.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-29 22:54:40 +07:00
shakespeare.diy
b3cda77224 Add optional crew band / music project to Skate Crews
Skate scenes and local music go hand in hand — crews can now list a band
name + a music link (Spotify/Bandcamp/YouTube/Nostr).

- SkateCrew type: new optional bandName / bandUrl fields.
- Parser reads `band` + `band-url` tags (band-url validated as http(s)).
- CrewForm: dedicated optional "Crew Band" section.
- CrewDetailPage: purple band row (linked when a valid URL is present).
- CrewsPage cards: small "Band" badge when a crew has a band.
- Kept scoped to crews only (not The Lab) since bands aren't a
  skate-specific craft.
- NIP.md: documented the new `band` / `band-url` crew tags.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-29 22:44:17 +07:00
shakespeare.diy
a371ff3645 Add Trick Glossary + harden Sasquatch AI rate-limit handling
Glossary:
- New /glossary page: 80+ tricks across 11 categories (fundamentals,
  flips, shoves/spins, grinds, slides, grabs, airs, lip, stalls,
  handplants, boneless, freestyle) with alternate names, difficulty
  badges, live search, and category filters.
- Added to desktop "More" dropdown and mobile burger menu.

Sasquatch AI:
- Fixed/expanded free-model fallback list with valid OpenRouter slugs
  (dropped the non-existent gemma-4 slug).
- Added exponential backoff + one retry on 429 rate limits, respecting
  Retry-After headers.
- SasquatchGuide now reuses the shared sasquatchBrain caller instead of
  a duplicated implementation.
- Friendlier fallback message when the AI is saturated.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-29 22:39:51 +07:00
shakespeare.diy
5ec1c84ef0 Fix Near Me crash on GrapheneOS: avoid Play-Services position API
Root cause: @capacitor/geolocation v8 fetches position via Google Play
Services (play-services-location / iongeolocation-android). On de-Googled
devices (GrapheneOS) those classes don't exist, so the plugin's
getCurrentPosition/watchPosition throw NoClassDefFoundError on the Android
main thread -> FATAL EXCEPTION -> app killed. No JS try/catch can intercept it.

Fix: use the native plugin ONLY for the OS location-permission prompt (pure
Android, no Play Services), then fetch the actual position via the WebView's
navigator.geolocation, which uses Android's LocationManager directly and works
without Google. Removed the now-dead raceForFirstFix() and the native
position/watch typings so the Play-Services code path is never invoked.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 14:08:23 +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
59a801d525 Geolocation: GrapheneOS-correct fix — parallel hi/lo watches, 60s, progress
The persistent failure on GrapheneOS (no Play Services, GPS-only) was
caused by the acquisition strategy itself, not the OS:

- The old low-accuracy-only watch never fired without a fused/network
  provider, and the 25s deadline killed it before a cold GPS lock (which
  takes 30–60s on GrapheneOS) could ever deliver.

Rewrite raceForFirstFix to actually work on GPS-only devices:
- Run BOTH a high-accuracy (GPS) and low-accuracy (network) watch in
  parallel — high accuracy is what engages the GPS chip and is usually
  the one that delivers on GrapheneOS.
- Also fire a one-shot high-accuracy getCurrentPosition.
- Extend the deadline to 60s (real cold-GPS lock time) and classify a
  timeout-flavored failure correctly.
- Ignore invalid/null-island (0,0) readings.
- Drop the redundant post-race extra attempt.

UX: show a persistent "Locating you…" toast (dismissed on success/fail)
so the up-to-60s GPS lock doesn't look frozen, plus a "Found you"
confirmation. Failure still surfaces the raw error text for diagnosis.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 00:36:16 +07:00
shakespeare.diy
a3dad2cbec Geolocation: handle 'no provider' case (GrapheneOS / no Play Services)
Root cause of the persistent Near Me failure: the test device runs
GrapheneOS with no Google Play Services, so there's no network/fused
location provider — only raw GPS, which can't fix indoors. This was
never a code bug.

- Add 'no_provider' GeoErrorKind; detect it from native error text
  (disabled / provider / settings / unavailable).
- Show accurate, actionable messaging: tell the user to enable Network
  Location (Settings → Location) — the GrapheneOS-friendly fix — or use
  GPS outdoors, instead of a vague timeout.
- Refine the timeout copy along the same lines.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 00:32:03 +07:00
shakespeare.diy
700ebee17e Geolocation: fast first-fix via watchPosition race (Google-Maps-like)
The real reason Near Me failed indoors: getCurrentPosition with high
accuracy waits for a single satisfactory GPS reading, which needs
satellite visibility and times out indoors. Google Maps feels instant
because it takes the FIRST update from any provider (wifi/cell/fused).

Rewrite the acquisition strategy to do the same:
- raceForFirstFix() starts a low-accuracy watchPosition and resolves on
  the very first update (the fast network/fused fix, ~1s indoors),
  racing it against a cheap cached getCurrentPosition. First to return
  wins; watch is cleared immediately after.
- 25s deadline falls back to one long high-accuracy attempt for the
  cold-outdoor-GPS case.
- Apply the same watchPosition-race to the web path.
- watchPosition/clearWatch added to the plugin proxy interface, guarded
  so builds without them fall back to getCurrentPosition.
- Update timeout toast (drop the "walk to a window" advice; point at the
  GPS/Location quick-setting toggle instead).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 23:30:17 +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
9c7cd5ec4e Improve geolocation reliability: 3-stage fix with forgiving timeouts
Now that the native crash is fixed (Kotlin 2.1 alignment on the Android
side), the remaining issue was GPS "Location timed out" on a cold first
fix / indoors. Rework the native position acquisition into three
increasingly-forgiving stages that return the first success:

  1. INSTANT — accept a cached/coarse fix up to 5 min old (resolves
     immediately on a warm chip).
  2. PRECISE — high-accuracy GPS with a generous 30s window so a cold
     first-fix has room to complete.
  3. COARSE FALLBACK — network/wifi fix with the longest window to
     rescue the "GPS can't lock indoors" case.

Also bump the web-path timeout to 20s, and make the "Location timed out"
toast actionable on native (suggests stepping near a window / retrying).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 22:47:48 +07:00
shakespeare.diy
71e5c5df1f Harden native geolocation to prevent app-crash on "Near Me"
The "Near Me crashes the app / bounces to ZapStore" bug is a native
FATAL EXCEPTION on the Android main thread, which no JS try/catch can
intercept. registerPlugin('Geolocation') always returns a proxy even
when the native class isn't actually registered in the APK (e.g. after
an incomplete cap sync or missing play-services-location), and invoking
a method on that ghost proxy is what detonates the crash.

Fix: verify the plugin is genuinely registered via
Capacitor.isPluginAvailable() and the live window.Capacitor.Plugins
registry BEFORE ever calling it. If it's not truly present, fall back to
the WebView's navigator.geolocation instead of crashing. Also guard the
requestPermissions step independently so a permission-flow throw can't
take down the app. isNativeGeolocation() now reflects the path actually
in use so error copy matches; added isNativePlatform() helper.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 22:16:25 +07:00
shakespeare.diy
59493ac784 Geolocation: coarse fallback + clearer native error copy (v1.0.75)
The native Geolocation fix only takes effect once the APK is rebuilt — the
current phone build still uses navigator.geolocation, hence the error. To make
the rebuilt version more robust:

- If a high-accuracy fix fails/times out (common indoors or weak GPS), retry
  with a coarse (network/wifi) fix and a longer window before giving up. This
  rescues most "Location unavailable" cases.
- On native, the "unavailable" toast now tells the user to turn on Location/GPS
  AND allow the app permission, instead of the generic web message.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 21:17:20 +07:00
shakespeare.diy
04b8b0a8b1 Amber signing: add timeout + one-time setPackageName so hangs surface as errors (v1.0.74)
Symptom: crew logo upload spins forever, and crew creation appears to do
nothing. Both go through Amber signEvent. Login (getPublicKey) works because
it uses Amber's silent content-resolver path, but signEvent must launch
Amber's approval Activity and wait for the result — and if that result never
comes back (buggy plugin build, user backs out, missing permission) the
bridge promise never settles, so the UI hangs with no error.

Changes:
- withTimeout() wraps every Amber bridge call (sign/getPublicKey/nip04/nip44)
  with a 90s cap. A hung round-trip now rejects with a clear, actionable
  message instead of spinning forever, which also unblocks the CrewForm
  (isUploading/isPublishing finally resolve).
- setPackageName is now called ONCE per session (cached promise) instead of
  before every op — repeated calls are needless bridge chatter and can wedge
  on some builds.

NOTE (root cause, fixed on the build machine): package.json requests the
GitHub `main` build of nostr-signer-capacitor-plugin, but package-lock.json
still pins the old npm 0.0.5 (which the APK bundles). 0.0.5 is the version
prone to the signEvent activity-result hang. Regenerate the lockfile with
`npm install` so the `main` build is used — see chat for steps.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 21:13:58 +07:00
shakespeare.diy
b3a025ca77 Native geolocation for Near Me + desktop map fits viewport with footer (v1.0.73)
1. Near Me "location blocked" on the APK:
   - navigator.geolocation returns PERMISSION_DENIED inside the Android WebView
     because the WebView never gets the OS location permission, so the app
     showed a bogus "your browser blocked location" help sheet.
   - New src/lib/geolocation.ts: platform-aware getCurrentPosition(). On native
     it uses Capacitor's Geolocation plugin via registerPlugin('Geolocation')
     from bundled @capacitor/core (same runtime pattern as the camera/Amber
     fixes) and calls requestPermissions() to trigger the real Android prompt;
     on web it falls back to navigator.geolocation.
   - MapPage handleNearMe, proximity sort, and quick-snap location now use it.
     On native, a denied permission shows an OS-permission toast instead of the
     browser help sheet.
   - Added @capacitor/geolocation dependency (native side picked up via cap sync;
     no bare import in the JS bundle).

2. Desktop map didn't fit the viewport (had to scroll to see footer):
   - Map wrapper was md:h-[calc(100vh-4rem)] — full height minus header only,
     leaving no room for the footer.
   - Added --footer-desktop token (5rem) and changed the desktop height to
     calc(100vh-4rem-var(--footer-desktop)) so map + footer fit without scroll.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 20:28:06 +07:00
shakespeare.diy
def67d4df2 Fix "Capacitor Amber plugin not available" on upload/signing (v1.0.71)
Root cause (same class as the camera bug): capacitorAmberSigner.ts used
`await import('nostr-signer-capacitor-plugin')`, but that package is marked
external in vite.config.ts and the native build uses inlineDynamicImports, so
the bare specifier survives in the APK bundle and the Android WebView can't
resolve it at runtime. The import threw, getPlugin() returned null, and every
Amber signing op — including the Blossom upload auth event that a Crew logo
upload needs — failed with "Capacitor Amber plugin not available".

Fix: access the native signer via Capacitor's registerPlugin('NostrSignerPlugin')
from the bundled @capacitor/core, and reimplement the plugin's thin JS wrapper
logic directly against the native bridge (options-object API per the plugin's
main source). This removes the broken dynamic import entirely and is version-
independent as long as the native plugin is registered. Covers both native
Amber login (getAmberPublicKeyViaPlugin) and all signing/encryption paths.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 19:57:55 +07:00
shakespeare.diy
939a101b38 Fix Quick Snap opening gallery instead of camera on native (v1.0.70)
Root cause: quickCapture.ts relied on window.Capacitor.Plugins.Camera,
which is only populated once something imports '@capacitor/camera'. Since
we deliberately never import that package (to avoid the old white-screen
crash), the plugin was never registered, the native check failed, and Quick
Snap fell through to the web file input — opening the gallery.

Fix: use Capacitor's official registerPlugin('Camera') from the bundled
@capacitor/core. This returns a proxy that dispatches to the native Camera
implementation over the bridge without needing the JS @capacitor/camera
package to self-register, and without putting a bare '@capacitor/camera'
specifier in the bundle. Applied the same pattern to CameraCapture.tsx.

Also: on a permission error, show a clear "enable camera access" toast
instead of silently opening the gallery.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 19:47:36 +07:00
shakespeare.diy
6d0480961c Quick Snap: camera-first on native (runtime Camera plugin), file input on web
The previous file-input-only approach fixed the crash but opened the gallery
on native because capture="environment" is only a hint the OS can ignore.

New platform-aware flow (lib/quickCapture.ts):
- Native (APK): open the OS camera directly via the runtime-injected
  window.Capacitor.Plugins.Camera with source:'CAMERA' — forces the real
  camera and gives built-in preview/retake. Accessed off the runtime object,
  so no '@capacitor/camera' specifier in the bundle (keeps the white-screen
  fix intact). User cancel is treated as a no-op.
- Web: falls back to the file input with capture="environment".
- If the native camera errors, gracefully falls back to the file input.

MapPage Quick Snap now routes through handleQuickSnap() → the captured photo
feeds the existing upload → create-spot flow unchanged. Bump 1.0.69.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 19:38:46 +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