Commit graph

700 commits

Author SHA1 Message Date
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
67d8a24d73 Fuse skater profile (35000) with Nostr identity (kind 0)
Make the skater layer and the universal Nostr profile feel like one person,
with kind-0 as the single source of truth for shared fields.

1. Public profile (NIP19Page): render the skate layer inline — stance, level,
   styles, years, home spot as a branded card right under the About. Own-profile
   nudge ("Add your skate details") when no 35000 exists. Website in the sidebar
   now falls back to the skater profile when kind-0 has none.

2. Become-Skater: prefill bio/website from kind-0 and default the photo to the
   kind-0 avatar (with "using your profile photo" affordance). Store shared
   fields ONLY as overrides — matching values are dropped so kind-0 stays
   authoritative and nothing goes stale. "From your profile" hints + a
   linked-to-your-profile banner make the relationship explicit.

3. Fallback-on-render for website/image/bio wherever the skater field is empty.

4. Bidirectional cross-links: new SkaterDirectoryCard in Settings > Profile
   shows directory status (level + style chips) linking to edit, or nudges the
   user to join; Become-Skater links back to full-profile editing.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 21:11:36 +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
9599adaf0d Redesign profile edit form UI/UX + per-line sponsors
- Restructure EditProfileForm into clean, titled sections (Identity,
  Bitcoin & Payments, Skate Profile, Account) with gradient icon
  headers, consistent spacing, and a reusable FieldRow with inline
  icon + validation status.
- Sponsors is now a dynamic per-line list (add/remove rows, Enter to
  add) instead of a textarea; stored newline-separated. The profile
  renders each sponsor as its own badge.
- Lightning address placeholder is now generic (you@yourdomain.com) —
  no specific wallet names anywhere.
- Nicer image upload: click-the-thumbnail-to-upload with hover overlay,
  round avatar preview / wide banner preview.
- Sticky Save bar at the bottom of the form.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 20:06:32 +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
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
889f6311ed Mobile bottom nav: Profile tab now opens your profile, not Settings
The avatar "Profile" tab in the mobile bottom bar linked to /settings,
so tapping your own face took you to Settings instead of your public
profile. It now routes to /:npub (View Profile) when logged in, and
still falls back to /settings when logged out (where login lives).
Long-press to customize the nav is preserved.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 19:23:56 +07:00
shakespeare.diy
9086123240 Fix messy Stash header/toolbar layout on mobile
- Let the title badges (Encrypted · Syncs everywhere · sats) wrap
  gracefully instead of colliding with the toolbar controls
- Hide the list/grid view toggle on the empty state (nothing to toggle)
  so it no longer clutters the space next to the Add button
- Give the title row room to grow on mobile (min-h-14 + padding)
  instead of forcing everything into a fixed 56px row
- Make the search input flex-fill and shrink the category select on
  small screens so the filter bar no longer overflows
- Add aria-labels to the view-toggle buttons

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 19:17:40 +07:00
shakespeare.diy
955d5902d5 Add RK69 DIY Skatepark to Supporters / Friends
Links to https://www.instagram.com/rk69diyskatepark, placed next to SNT4EVER.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-31 19:42:16 +07:00
shakespeare.diy
538ae4dfb1 v1.2.2: bump version + release notes (Sasquatch left panel, full data coverage, Judges, clickable stats)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 16:30:25 +07:00
shakespeare.diy
a0c8493502 Sasquatch: open the panel on the LEFT again on desktop
- Panel now anchors md:bottom-16 md:left-4 (was bottom-right) so it opens on
  the left as before, and sits ABOVE the desktop footer so it never covers the
  "Made with..." row or footer links.
- Tightened max-height to clear the top nav too (never slides under the menu).
- Bubble stays bottom-right on desktop for discoverability (clear of footer).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 16:28:07 +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
e996d96fa0 Sasquatch: fix desktop discoverability + repetitive greeting
- Move bubble & panel to bottom-RIGHT on desktop (was bottom-left, colliding
  with / hiding behind the desktop footer). Mobile stays bottom-left.
- Raise bubble/panel z-index to z-60 so it always sits above the footer.
- Re-clamp a stale saved drag position on mount: a bubble dragged off-screen
  on one device no longer disappears on another.
- Stop showing the long welcome greeting + action button on every open once a
  conversation has started (chatMessages > 0), so it no longer "says the same
  thing all over again".

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 15:51:42 +07:00
shakespeare.diy
d12af9e56d Update release notes for v1.2.1 (Judges tab + clickable community stats)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 15:36:33 +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
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
67597dc767 Bump version to 1.1.8 for Zapstore release (Amber native detection + GrapheneOS <queries> manifest fix)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 14:10:21 +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
3dea1e42dc Bump version to 1.1.6 for Zapstore release (crew UI, Amber login/signup, Stash fixes)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 13:37:28 +07:00
shakespeare.diy
90a6af810e Make Amber login/signup the polished, no-copy-paste primary flow (NIP-55)
Login (Amber tab):
- Emphasize the proper NIP-55 redirect flow: one tap → approve in Amber →
  redirected back to /amber-callback → poll auto-completes login. No copy/paste.
- Replace the indefinite spinner with a clear "Waiting for Amber…" state that
  auto-signs-in on return, with an "Open Amber again" retry.
- Demote manual pubkey entry to an explicit "Having trouble?" escape hatch that
  the user must tap — it never hijacks the flow anymore.
- Removed the aggressive 2.5s auto-switch to paste; keep background polling on
  both waiting and paste states so a late callback still completes.

Signup:
- Welcome step now recommends "Sign up with Amber" first on Android (key lives
  inside Amber, SkateSpots never touches it), with a clearly-secondary "quick
  account on this device" option. Wired onUseAmber through LoginArea to open the
  Amber login flow.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 13:35:13 +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
945678c957 Fix vendor-react chunk crash: keep React core with react-dom
The web manualChunks put react-dom/react-router in a vendor-react chunk
but left core `react` (useLayoutEffect etc.) in a different chunk, so
react-dom executed before React was defined:
  "Cannot read properties of undefined (reading 'useLayoutEffect')".
Group react, react-dom, scheduler, react-router and jsx-runtime into the
same vendor-react chunk so the React runtime loads together and first.

Note: Capacitor APK builds must use CAPACITOR_BUILD=1 (single inlined
bundle) via `npm run android:build` — the plain `npm run build` emits the
chunked web output that breaks in the Android WebView.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 12:50:53 +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
f836763e75 Replace app logo with new orange starburst emblem across all contexts
Swapped in the new brand logo for every place the app icon appears:
- Favicon, apple-touch-icon, and og:image / twitter:image (via index.html
  references to /images/icon-1024.png)
- PWA manifest icons (any + maskable) and all app shortcuts
- In-app header logos (Layout, Index) and logo.png / logo-icon.png
- This same icon-1024.png is what the Zapstore listing pulls, so the store
  entry updates too

Also corrected the manifest icon "sizes" declarations to match the actual
asset dimensions (180x180) so browsers don't reject the icons for a
size mismatch.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 11:27:02 +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
37f07931d9 Bump version to 1.1.3 for Zapstore release (one-tap signup + recovery-code retrieval)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 11:16:47 +07:00
shakespeare.diy
f1cb2a32ba Add recovery-code retrieval to Settings > Security (closes the deferred-backup loop)
Since signup now defers the key backup, users who skip it MUST be able to get
their recovery code later — otherwise we've just moved the trap. This adds a
"Your Recovery Code" card at the top of Settings > Security that:

- For nsec logins: reveals the recovery code on demand (hidden by default),
  with Copy, Download (.txt), and QR options, plus a Hide button and clear
  "never share this" warnings.
- For bunker / browser-extension / Amber logins: explains that SkateSpots
  never held the key and points the user to back it up in their signer app.

Also removed the old placeholder QR handler that only showed an error, and
updated the "Backup Not Confirmed" copy to direct users to the new reveal
card instead of falsely claiming lost keys are unrecoverable.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 11:10:51 +07:00
shakespeare.diy
54e6b4a809 UX Move 1: kill the signup wall — one-tap silent account, no forced key download
- "Sign Up" now goes straight into a one-tap flow: a key is generated silently
  and the user is logged in immediately, landing on optional profile setup.
- Removed the method-choice dialog (mnemonic vs nsec) and the generate/download
  steps from the default path — no crypto vocabulary, no gates.
- Removed the forced key-download gate (Continue was disabled until download).
  Backup is now optional and deferred (BackupReminder already nudges post-login;
  the "done" step offers a gentle, jargon-free recovery-code reveal).
- Softened copy throughout: "secret key" -> "recovery code / account backup",
  "your identity on Nostr" -> plain reassurance, CTA -> "Save & Start Skating".
- Advanced signer-app path preserved under a subtle link.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 11:03:26 +07:00
shakespeare.diy
33cfe692ed Add privacy transparency: dedicated /privacy page, onboarding privacy note, Sasquatch VPN/Tor Q&A, footer link
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 10:46:47 +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
86a5a4b859 #2 Silence AmberCallback mount-time console spam (keep flow-error logs)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 10:18:32 +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
b5b9faeec8 #1 Onboarding polish: add 'Why bother?' skater value-prop section, fix dead /radio link (-> Crews), soften Nostr FAQ overclaim
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 10:02:03 +07:00
shakespeare.diy
49325be38e #5 Reframe 'permanent/can't delete' copy around user data ownership & relay choice (onboarding, relay education, about)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 09:59:43 +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
1c4987c8bf Add desktop-only "Get on Zapstore" CTA to map Welcome Banner
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-20 19:41:53 +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
c683b7e8bf Add clinkme.dev specs and apps page links across the app
- NofferField: link → https://clinkme.dev/specs.html (instead of just /)
- EditProfileForm: link → https://clinkme.dev/specs.html (instead of just /)
- SasquatchGuide: added [specs] and [apps] inline links next to the clink description, pointing to the new pages

The app already referenced clinkme.dev homepage; these add the two new deeper pages for discoverability.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-19 15:13:32 +07:00
shakespeare.diy
09418ac660 Replace cashumints.space links with mintradar.pedani.eu across wallet UI
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-18 20:49:10 +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
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
df6b6fdf92 Ship CSP in the bundle so it also protects the native (mobile) build
Follow-up to the map-XSS work. The Capacitor app ships the same dist/ bundle
(webDir: 'dist') and renders the same untrusted relay content, so it shared
the same injection surface — but the CSP only existed as HTTP headers
(public/_headers, netlify.toml), which never reach a WebView loading from a
local scheme. The prior "OS sandbox is enough" comment was wrong: the sandbox
isolates the app from OTHER apps; it does not stop a same-origin injected
script from reading this app's own localStorage (nsec, NWC string, Cashu seed)
or IndexedDB (Cashu bearer ecash proofs, native-only wallet).

Changes:
- index.html: add a <meta http-equiv="Content-Security-Policy"> so the policy
  ships inside the bundle and applies to BOTH web and native. script-src stays
  'self' 'wasm-unsafe-eval' (no unsafe-inline/eval). Capacitor local origins
  (https://localhost, capacitor://localhost, ionic://localhost) are allowlisted
  on script/style/img/connect so asset loading & live-reload still work.
  Replaces the old "CSP intentionally omitted" comment. Verified the app boots
  and the map renders with no CSP violations.
- SECURITY.md: correct the CSP section (now three synced locations incl. the
  meta tag; explain the local-scheme reason and the OS-sandbox misconception),
  add Cashu ecash/seed to the threat model and residual-risks, note the shared
  bundle, and extend the auditor file map (useCashu.ts, platform.ts,
  capacitor.config.ts).

Note: the actual injection fix (createElement + textContent in map popups)
already shipped on mobile since it lives in the shared bundle; this commit
adds the defense-in-depth layer that was previously web-only.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-16 22:30:19 +07:00
shakespeare.diy
d9cf98848e Add SECURITY.md for auditors + align netlify.toml CSP with public/_headers
- Add root SECURITY.md documenting the threat model (plaintext nsec / NWC in
  localStorage inherent to client-side Nostr), reporting channels (Nostr DM /
  NIP-05), in/out of scope, the CSP and its trade-offs, untrusted-data
  rendering rules, URL/CSS sanitization, author-scoped queries, NWC budget
  guidance, accepted residual risks, and an auditor file map.
- netlify.toml now serves the same Content-Security-Policy as public/_headers
  and uses X-Frame-Options: DENY + Permissions-Policy, so the hardening holds
  regardless of which static host serves the web build.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-16 22:20:47 +07:00