Commit graph

356 commits

Author SHA1 Message Date
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
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
834f33d53e Make Sasquatch bubble open reliably on first tap (mobile)
The "1st tap does nothing, 2nd/3rd works" bug came from relying solely on
pointer-capture tap detection, which is flaky on the first interaction after
mount in mobile WebViews — and the earlier e.preventDefault() on pointerup was
suppressing the click we could otherwise use.

Fix: add a plain onClick as a reliable open path (the most dependable "tapped"
signal in a WebView), extract an idempotent openPanel() used by both pointerup
and click, and drop the preventDefault so the click isn't swallowed. The
justOpenedRef lock still blocks the ghost-click that would re-close it. Double
firing is harmless since openPanel just sets visible=true.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 15:36:11 +07:00
shakespeare.diy
ebbd0fd125 Fix Sasquatch panel opening then instantly closing on mobile
On touch devices, the bubble's pointerup opened the panel and unmounted the
bubble; the synthesized "ghost click" from the same tap then landed on the
panel's minimize/close button (which renders in roughly the same bottom-left
spot), instantly re-closing it.

Fix: preventDefault/stopPropagation on pointerup to suppress the ghost click,
guard releasePointerCapture against unmount throws, and add a 400ms
"just opened" lock so stray close/minimize events from the same tap are
ignored.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 15:24:05 +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
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
90d300d5dd SECURITY: remove committed nsec from ZAPSTORE.md
Replace the hardcoded signing nsec with a `nsec1...` placeholder and add a
warning to always pass SIGN_WITH inline at runtime, never commit it.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 13:31:09 +07:00
shakespeare.diy
a51901e54c Document recurring Zapstore release flow in ZAPSTORE.md
Add a prominent "Releasing a new version" runbook (bump both package.json
and build.gradle, git pull, npm run android:build, SIGN_WITH=... zsp publish
zapstore.yaml) with troubleshooting table. Keeps first-time keystore/setup
steps below for new machines.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 13:26:41 +07:00
shakespeare.diy
6dc7de9e5c Add @capacitor/cli and @capacitor/android for native Android builds
Fixes 'npx cap: could not determine executable to run' and the Gradle
'No variants exist for :capacitor-android' error by tracking the Capacitor
build tooling in package.json so it survives npm install.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 13:23:32 +07:00
shakespeare.diy
d3ae0672b7 Bump version to 1.0.86 (sync with ZapStore, latest published was 1.0.85)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 13:21:07 +07:00
shakespeare.diy
b2aba6322c Bump version to 1.0.77 for ZapStore release
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 13:19:28 +07:00
shakespeare.diy
7d37a5effd Make goskatespots.nostr1.com the top-priority catching relay
- reqRouter now always queries the community relay (SKATE_RELAY) first,
  even when a user hasn't added it to their read list, so curated skate
  content takes priority in NPool's first-seen dedup ordering
- eventRouter publishes to the catching relay first, then user write relays
- Gated behind the existing useSkateRelay setting (default: true)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 13:14:50 +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
5a5acda985 Zapstore: refresh app description + release notes for v1.0.85
- zapstore.yaml description: add Crews, Spot Status Reports, Near Me
  locator, photographers/builders in The Lab, and local tours; remove
  NoGood Radio; freshen summary line.
- release-notes.txt: replace stale v1.0.62 platform-fee notes with
  current v1.0.85 highlights (Near Me crash fix + fast GPS, you-are-here
  pin, Spot Status Reports, camera-first Quick Snap, Amber signing fixes,
  crew/Lab deletion).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 11:34:36 +07:00
shakespeare.diy
f938a6c900 Newsletter #003: harder tone, extensive coverage of the full session
Expanded Drop #003 from 7 to 16 update cards covering everything actually
shipped this session (per git log), grouped: the geolocation war (crash
fix, parallel first-fix GPS, progress UI, you-are-here pin, crash-proofing,
no-provider messaging), Spot Status Reports (reporting, trust/recency
consensus, badges, gone-spot hiding), Quick Snap camera-first + snap→Add
Spot, Amber signing timeout + plugin fix, crew/Lab DELETION via NIP-09
(framed as the new deletion capability, not the existing features), and
native build plumbing.

Cranked the tone harder throughout and leaned fully into the "weekly
newsletter, shipped whenever the f*** we want" bit in both the vibe line
and masthead. Updated nostrText to match.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 01:10:54 +07:00
shakespeare.diy
35c7283970 Newsletter: add Drop #003 (geolocation saga + Spot Status Reports)
New issue covering everything shipped this session, written funny:
- Near Me crash fix (Kotlin 2.1 alignment)
- Parallel first-fix GPS strategy that works on GrapheneOS / no Play Services
- "Locating you…" / "Found you" progress feedback
- "You are here" user-location pin
- Community Spot Status Reports (active/bust/skate-stopped/closed/gone)
- Gone spots hidden from map markers
- Defensive native geolocation hardening

Also updated the masthead tagline to lean into the "weekly newsletter,
shipped whenever the f*** we want" bit. Includes ready-to-post nostrText
for Drop #003.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 01:06:24 +07:00
shakespeare.diy
58aaeb7b16 Map: add "you are here" user-location marker (SkateSpots logo + pulse)
After Near Me centers the map, there was no indicator of where the user
actually is. Add a Google-Maps-style location marker, brand-flavored:

- Circular badge using the SkateSpots logo (public/images/user-location.png)
  with a red (#ff0040) ring, sitting at the user's exact coordinates.
- Pulsing translucent accuracy halo (ssPulse keyframes) so it reads as a
  live "you are here" and doubles as subtle motion.
- Rendered above all other markers (zIndexOffset), with a "You are here"
  popup. Created/moved/removed reactively from a new `userLocation` prop.

Wired MapPage's userLocation state through to SpotMap (lat/lon → lat/lng).
Marker image path uses import.meta.env.BASE_URL so it resolves on both
web ('/') and the Capacitor APK ('./').

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 00:55:52 +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
8870a3e07f Near Me: surface raw geolocation error text on-screen for diagnosis
Append the underlying native/browser error message to the failure toast
(and log it) with a 12s duration, so the exact failure reason is visible
on-device without needing adb. This lets us distinguish OS Location-off,
provider-unavailable, timeout, and plugin errors at a glance.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 00:22:46 +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
7aad972099 Quick Snap: navigate to Add Spot immediately, upload photo on the form (v1.0.76)
Bug: after taking a Quick Snap photo and choosing "keep", the user never
reached the Add Spot form. handleQuickCameraCapture uploaded the photo to
Blossom FIRST (which needs an Amber signature and can be slow/fail) and only
navigated on success — so a stalled upload stranded the user right after the
camera.

Fix: Quick Snap now prioritizes speed —
- MapPage stashes the raw photo as a data URL (quickCapturePendingPhoto),
  grabs location (non-blocking), and navigates to /create-spot immediately.
- CreateSpotPage reads the pending photo on mount, converts it back to a File,
  and uploads it in the background with an "Uploading photo…" tile, adding the
  resulting URL to the images list. A failed upload now shows a toast on the
  form instead of blocking navigation. Old quickCaptureImage (already-uploaded
  URL) path kept for backward-compat.
- Removed the now-unused useUploadFile from MapPage.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 21:55:15 +07:00
shakespeare.diy
7395346228 Add .npmrc: map @jsr scope to JSR registry + legacy-peer-deps
Fixes local `npm install` failures on the build machine:
- @jsr:registry=https://npm.jsr.io — the Nostrify stack depends on JSR packages
  (@jsr/std__encoding, etc.) that don't exist on registry.npmjs.org; without
  this mapping npm 404s. The lockfile already resolves them via npm.jsr.io, but
  a fresh install from package.json needs the scope registry configured.
- legacy-peer-deps=true — resolves the long-standing benign peer-range lags
  (e.g. react-day-picker@8 peers date-fns 2/3 while the app uses date-fns 4)
  the same way the hosted build environment does, instead of ERESOLVE.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 21:26:54 +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
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
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
shakespeare.diy
87c710bf4a Quick Snap: use native camera/gallery input instead of custom camera
The custom in-app CameraCapture failed on the native APK ("Could not open
the camera") due to Capacitor camera permission plumbing, and a single
photo is all a spot snap needs. Replace the Quick Snap flow on the map with
a hidden native file input (accept="image/*" capture="environment"): one
tap opens the OS camera/gallery, the chosen photo feeds the existing upload
→ create-spot flow unchanged. Works identically on web and native, no
plugin/permission fragility. Removes the CameraCapture usage + import from
MapPage (component still used by CreateSpotPage).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 19:13:49 +07:00
shakespeare.diy
cc2e35f569 Lab tabs: fixed 5-across grid instead of horizontal scroll
The Lab category tabs (Coaches/Filmers/Photo/Editors/Builders) overflowed
the viewport and scrolled sideways on phones, hiding Builders. Replace the
overflow-x row with a 5-column grid of compact icon-over-label tabs so all
five are visible at once without lateral scrolling, with the count badge
floated to the corner so it never widens a tab.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 18:56:06 +07:00
shakespeare.diy
3a96184001 Fix native build: access Camera via runtime plugin, not @capacitor/camera import
Local CAPACITOR_BUILD failed: Rollup couldn't resolve import
"@capacitor/camera" from CameraCapture.tsx — the package is listed in
package.json but isn't present in node_modules on the build machine.

Root pattern (same class as the @capacitor/core crash): a package that
must NOT be resolved at build time was being imported by specifier.

Fix:
- CameraCapture.tsx no longer imports '@capacitor/camera' at all. The
  Camera plugin is accessed via the runtime-injected
  window.Capacitor.Plugins.Camera (Capacitor injects it natively), using
  string enum values ('dataUrl' / 'CAMERA'). No bare specifier remains in
  the bundle, and it degrades gracefully if the plugin is absent.
- vite.config: @capacitor/camera moved back to `external` (belt-and-braces;
  it's no longer imported anyway). @capacitor/core stays BUNDLED (static
  import in platform.ts — the white-screen fix). nostr-signer-capacitor-plugin
  and the 4 uninstalled @capacitor/* plugins remain external.

Verified: no @capacitor/camera specifier and no unresolved @capacitor/core
import in the built bundle; no static imports of any externalized package
remain in src/. Bump to 1.0.67.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 18:40:19 +07:00
shakespeare.diy
ac362a9a10 Fix native build: keep signer plugin external, bundle only @capacitor/core
v1.0.65 fixed the runtime crash (bundling @capacitor/core) but broke the
CAPACITOR_BUILD by also un-externalizing nostr-signer-capacitor-plugin —
which is installed from GitHub with a broken package entry point and
cannot be bundled ("Failed to resolve entry for package").

Correct split:
- BUNDLE @capacitor/core & @capacitor/camera (installed, statically
  imported, real JS the WebView needs) — this is the white-screen fix.
- KEEP EXTERNAL nostr-signer-capacitor-plugin — no valid entry point;
  only used via a guarded dynamic import() at runtime on native
  (try/catch → null on failure), so external is correct and safe.
- KEEP EXTERNAL the four non-installed @capacitor/* plugins.

Verified: built bundle has no bare static @capacitor/core specifier;
signer plugin remains an external dynamic import. Bump to 1.0.66.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 18:36:45 +07:00
shakespeare.diy
0a47bb47d2 Fix native crash: bundle @capacitor/core instead of externalizing it
The boot crash guard surfaced the real root cause on-device:
  TypeError: Failed to resolve module specifier "@capacitor/core"

@capacitor/core and @capacitor/camera were marked `external` in the
Rollup config, which left bare `import "@capacitor/core"` specifiers in
the output bundle. A native WebView has no module resolver for bare
specifiers, so boot crashed before React mounted.

Fix: remove the INSTALLED Capacitor packages from `external` so their JS
API layer is bundled (the native implementation is still injected at
runtime via window.Capacitor). Only the four NON-installed packages
(@capacitor/app, haptics, keyboard, status-bar) stay external — they are
never statically imported (accessed via window.Capacitor.Plugins), so
this can't break the build.

Bump to 1.0.65.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 18:31:26 +07:00
shakespeare.diy
95afd9551f Bump version to 1.0.64 (native white-screen fix + boot crash guard)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 18:22:48 +07:00
shakespeare.diy
5334144d27 Fix native white screen: relative asset base + boot crash guard
Two changes targeting the post-update white screen on the Zapstore APK:

1. vite.config: set base './' when CAPACITOR_BUILD is set. In a native
   WebView the bundle is served from the local filesystem, so absolute '/'
   asset paths are the classic cause of a silent white screen. The web
   deploy keeps '/'. This is the documented Capacitor requirement and is
   zero-risk to the working web build.

2. index.html: add a dependency-free "boot crash guard" that runs before
   the app bundle and paints any uncaught error / unhandled rejection —
   plus a 12s "did not render" timeout — directly to the screen using
   inline styles only. A boot failure that would otherwise be an
   info-less white screen now shows the actual error ON DEVICE, with no
   USB/remote-debugging needed. main.tsx signals a clean boot to cancel
   the timeout guard.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 18:21:57 +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
5cde2a640d Bump version to 1.0.63 for Zapstore release
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 17:45:08 +07:00
shakespeare.diy
e76076c1d9 Fix missing DialogTitle/SheetTitle accessibility warnings
Add visually-hidden (sr-only) titles to the three rendered dialogs/sheets
that were missing them:
- OnboardingPage relay-setup dialog (RelayEducation)
- Layout mobile navigation sheet
- MapPage spot detail bottom sheet

These satisfy Radix's accessibility requirement (screen readers announce
the dialog purpose) without altering the visual design.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 17:41:25 +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
a0c59375ed Add crew chat moderation (client-side hide for E2EE group DMs)
- CrewChat: founder+admins can hide chat messages via a per-message hide button; reuses useCrewModeration/useHideCrewPost with the rumor (kind-14) message id
- Hidden messages filtered from chat for everyone; moderators see them greyed + "Hidden" (transparent/reversible)
- Since NIP-17 messages are E2EE and can't be deleted from relays, moderation is enforced client-side via public NIP-32 labels (label reveals only the hidden id, never content)
- Wire admins/crewCoord/canModerate from CrewDetailPage; header hint for moderators
- NIP.md: expand moderation section to cover both feed (kind-1 note id) and chat (kind-14 rumor id) with the E2EE client-side caveat

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 17:08:18 +07:00