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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
- 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>
- 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>
- Add mintReachability.ts: probe a mint's /v1/info from the user's browser to detect CORS-blocked ('cors-blocked') vs down ('unreachable') vs 'ok'
- useMintManager.handleAddMint: probe before adding; reject CORS-blocked/unreachable mints with a clear message instead of letting them fail later on invoice
- useCashu default mint: instead of hardcoding one (unverifiable at build time), probe an ordered list of web-friendly candidates and add the first that's reachable in-browser; minibits excluded (mobile-app only)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- useCrewModeration: fetch NIP-32 `hidden` labels authored by founder+admins for a crew; useHideCrewPost publishes a kind-1985 hidden label (L/l org.skatespots.moderation, e=post, a=crew)
- CrewDetailPage feed: moderators can hide posts via an EyeOff button + confirm dialog; hidden posts filtered from the feed for everyone, shown greyed/"Hidden from feed" to moderators (reversible/auditable)
- CrewInviteDialog: founder+admins share an invite link/message (native share + copy); founder gets a shortcut to add a member key in crew settings
- Gate all admin actions on useCrewRole().canManage; moderators = founder + delegated admins
- Add MODERATION_LABEL_NAMESPACE/HIDDEN constants
- NIP.md: document feed moderation label format (author must be founder/admin) + invite semantics
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Root cause: mint.minibits.cash sends no Access-Control-Allow-Origin header, so browsers block it (NetworkError → "Failed to create invoice / Failed to fetch mint"). Default to CORS-friendly mint.coinos.io instead.
- useLightningOperations: detect CORS/network failures on receive & send, show actionable "Can't reach this mint — switch mints" message instead of a dead end.
- MintPrivacyManager: reorder recommended mints by browser reachability; flag Minibits as mobile-app-only (not web-reachable).
- useHistoryManager: fix `mintQuoteRepository.getAll is not a function` crash by probing for whichever list method the installed coco-cashu version exposes (getAll/getAllQuotes/list/...), failing gracefully.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- SkateCrew: add nip05 + admins fields; parse `admin` role marker on p tags (admins = subset of members)
- CrewForm: NIP-05 input + per-member "Make admin" toggle; write nip05 tag + admin-marked p tags (works on create & edit via shared form)
- useCrewNip05: verify crew handle on ANY domain against the founder pubkey (verified/mismatch/unreachable), CORS-proxy fallback + caching
- CrewNip05Badge: green ✓ / red ⚠ / muted handle states with tooltips
- CollectiveMembers: show Admin chips, sort admins first, admin count in summary
- useCrewRole: centralized founder/admin/member/none roles (canManage, canEditProfile) — founder owns identity, admins get delegated powers with their own keys; egalitarian shared-editing intentionally unsupported
- CrewDetailPage: NIP-05 badge by crew name, viewer role badges (admin/member)
- NIP.md: document crew nip05 verification, admin role marker, and governance/conflict-resolution model
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Lab:
- useLabProfiles sorts featured (Pro) profiles first
- ProBadge shown on Lab cards (LabPage) + Lab detail page; featured cards
get a subtle amber highlight
Events (same no-paywall model as Lab):
- New FeaturedEventUpgrade: every event posts for free; optional fee adds a
featured badge + top placement (reuses existing featuredEvent fee)
- CreateEventPage: removed the paid-event fee gate; opt-in featured toggle,
writes a `featured` tag when active
- useSkateEvents sorts featured events first; EventCard shows ProBadge
(compact in list view, overlay on the image in full view)
Docs:
- NIP.md documents the `featured` tag (unix-seconds expiry, self-attested)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Basic Lab profiles are now always free to publish. The 5,000 sat fee is
opt-in and unlocks a Pro badge + featured placement instead of gating
publication behind a rate.
- types.ts: `featuredUntil` field on all 5 Lab types + SkateEvent,
parseFeaturedUntil() + isFeaturedActive() helpers, wired into parsers
- usePlatformFee: exposes featuredUntilSeconds for the `featured` tag
(platform owners get a 1yr window)
- New ProBadge + LabProUpgrade (opt-in toggle → reveals PlatformFeePayment)
- All 5 Become pages: removed rate-based fee gate and wallet-required gate;
publish is free; when Pro is active a `["featured", "<expiry>"]` tag is added
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Built on the existing NIP-17 gift-wrapped group DM system (useGroupConversation
+ useSendDirectMessage), so the relay only ever sees encrypted blobs.
- New CrewChat component: message list w/ author profiles, composer
(Enter to send), auto-scroll, skeleton/empty states
- Participants = founder + all listed members (deduped)
- Only crew members can read/write (others see a locked "ask to join" state);
warns if the signer lacks NIP-44
- "Private" lock badge makes the E2E-encrypted nature unmistakable
- Surfaced as a card on the crew detail page under the Crew Feed
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Makes it unmistakable that Spot Chat messages are public and unencrypted:
- Amber "🌐 Public" badge next to the title with an explanatory tooltip
- Footer note reworded to point users to Direct Messages for private chats
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- useGrowthStats now queries kind 35004 (photographers) and 35005 (builders),
which were previously omitted; added to totals, monthly buckets, allContent,
and the content-mix donut (folded into the "Lab" slice)
- Community Stats page reorganized into clear sub-sections:
* "The Lab · Creators & Services" — Coaches, Filmers, Editors,
Photographers, Builders, with a total badge
* "Community" — Tours, Crews
This scales the growing role list cleanly instead of cramming one row.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Spot Chat:
- Compact TrustFilter dropdown in the chat header (All / Verified / Trusted / Following)
- Filters messages by author trust via useBatchTrustScores; own messages
always stay visible; shows a "N hidden by your trust filter" indicator
Community stats:
- useGrowthStats now queries kind 35006 crews (was missing entirely)
- totalCrews added to totals + monthly buckets + content-mix donut
- New "Crews" StatCard on the Community Stats page
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Inspired by BitChat's location-channel concept, reframed for skaters and
kept fully SkateSpots-native (own kind + tag conventions, no interop).
- New ephemeral kind 24899 tagged with a geohash `g` (documented in NIP.md)
- useSpotChat / useSendSpotChat hooks (polled, community-relay aware)
- SpotChat component: live message list, composer, author profiles,
Block/Neighborhood/City precision toggle, and light IRC-style flavor
(/me action messages, /who participant count)
- Spot detail pages now show a per-spot block-level channel
- Map gains a "Local Chat" sheet anchored to the map center with the
precision selector
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- useCVMClient: add findCrews (queries kind 35006 across data relays, filters by
city/country/vibe/search, returns CrewResult with member_count, vibes, url);
add crews to getCommunityStats + CrewResult type
- SasquatchGuide: register find_crews tool in system prompt + switch handler,
add crew keywords to needsLiveData, update app-facts (crews are searchable +
mention cross-linking and Zap-the-Crew)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- useCrewZap hook: splits a total evenly across recipients, resolves each
member's Lightning address (batched kind-0 query), fetches an invoice per
recipient and auto-pays via NWC → WebLN; per-recipient status
(resolving/paying/success/skipped/failed) with manual-invoice fallback
- splitAmount util distributes remainder fairly (no lost sats)
- ZapCrewDialog: amount presets + custom, per-head preview, live progress rows,
copy-invoice fallback when no wallet; excludes the current user
- CrewDetailPage: "Zap the Crew" button (recipients = founder + confirmed
members via useCollectiveMembers); shown only when >1 recipient
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Nutzap:
- Gate sending behind NUTZAP_SENDING_ENABLED=false with clear docs; sending
would require P2PK-locking proofs (fund-loss risk if wrong), so it's disabled
until implementable/auditable. Receiving + reading unchanged. No dead-end
button exists (hook was never wired to UI).
Cross-linking crews ↔ Lab collectives:
- types: parseAddrCoord/parseCrewRef/parseLabRefs helpers + LAB_KINDS; add
crewRef to all 5 Lab interfaces/parsers and labRefs[] to SkateCrew
- CrewLinkField (pick one of your own crews) added to all 5 Become-* forms,
shown only for collectives; publishes an a-tag on the Lab profile
- LabLinkField (pick your own Lab profiles) added to CrewForm; publishes a-tags
- CrossLinkCards: LinkedCrewCard on Lab detail ("Part of a Crew"), LinkedLabCard
on crew detail ("Hire This Crew"), resolving coordinates via useCrew/useLabProfile
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Remove LGBTQ+ and Longboard preset vibes
- Rename "Girls / Womxn" → "Girl / Woman"
- Add custom vibe/focus field: users define their own #hashtag focus
(normalized to a lowercase hyphenated slug, published as t tags)
- Support multiple "Other Socials" links (SkateCrew.socials[]) with add/remove UI;
parser collects all social tags; detail page renders each (URL or plain handle)
- Update NIP.md crew vibe taxonomy
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Phase 1 CLINK integration (recognize & display only):
- types.ts: optional noffer field + isValidNoffer guard on SkateCoach,
SkateFilmer, SkateEditor, SkatePhotographer, SkateShop; parsers read it
- NofferField: reusable opt-in input with live validation, added to all 4
Become-* Lab forms and both Create/Edit shop forms
- ClinkPayButton: secondary "Pay via CLINK" action (QR + copy + nostr: deep
link), rendered on CoachDetailPage and ShopDetailPage only when a valid
noffer is present; never replaces the existing zap / Lightning Address flow
- useQRCodeScanner: classify scanned noffer1/ndebit1 CLINK codes
- NIP.md: document the noffer tag on Lab kinds + shops and a CLINK section
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
New Photographers category (blue-indigo) — separate from Filmers:
- New addressable kind 35004 + SkatePhotographer type + parseSkatePhotographer.
- Rich photographer taxonomy as queryable `t` tags: services (action,
sequence, editorial, product, events…), camera bodies, lenses, lighting
(Profoto/Elinchrom/Godox/triggers…), support/rig, and shooting style,
plus a freeform "gear" tag and a portfolio link.
- BecomePhotographerPage form with chip-grid selectors for all gear
categories + "what's in the bag" textarea, wired to /become-photographer.
- The Lab: 4th "Photo" tab (indigo accent) with its own filters, cards,
empty state; OfferFAB gains "Offer Photography"; useLabProfiles fetches
35004; CoachDetailPage renders photographers (and editors) properly with
per-type accent, gear + portfolio display.
- NIP.md: documented the whole Lab (kinds 35001-35004) incl. photographer.
Filmer improvements (now video-focused, photography split out):
- Removed the token "Action Photography" service; added follow/line filming.
- Richer equipment list: cinema camera, HVX, action cam, gimbal, 4K/HFR.
- Filmers gain a freeform "what's in the bag" gear field + a reel/portfolio
link, both surfaced on the detail page.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Events can now be discovered directly on the map:
- New "Events" layer (lime #ccff00, calendar pin marker) plotting upcoming,
geo-located events. Off by default — users opt in via the Layers control.
- Event markers show a popup with title, date/time, location, and a
"View Event" button linking to the event detail page.
- Only future/ongoing events with a valid geohash are plotted (past events
are filtered out to keep the map clean).
- Added to the mobile Layers popover, desktop Layers popover, and the mobile
filter drawer's "Show on Map" grid. Layer count badge now tracks 5 layers.
- SpotMap gains `events` + `showEvents` props and a createEventIcon marker.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>