Commit graph

48 commits

Author SHA1 Message Date
SkateSpots
618d961f72 feat: relay consistency diagnostic and recent reputation 2026-10-31 12:11:40 +07:00
SkateSpots
66fc47cc28 feat: restore map footer, inline crew roles, Cordn reactions/push
- Keep the map footer links permanently visible; remove the hide toggle.
- Publish inline founder promote/demote from the member row.
- Add encrypted MLS reaction/delete/read control messages and UI.
- Add the minute Cordn join watcher: NIP-17 DMs + encrypted bell notices.
- Add the browser translation-matrix runner and a low-end performance finding.
2026-10-28 11:02:47 +07:00
SkateSpots
d2a24679f0 feat(chat): on-device translation pilot (English ↔ Spanish)
- @xenova/transformers 2.17.2 in a Web Worker; Opus-MT en↔es ONNX q8 packs
  self-hosted on the relay VPS (/models + CORS); ONNX Runtime 1.14 WASM
  self-hosted in public/ort/ (37MB) loaded from ../ort/.
- Hard network guard: only model GET/HEAD (no query) and /ort/ assets allowed.
  Message text travels only via postMessage; 9 tests prove fetch is never called
  with the text and every other request is blocked.
- TranslateMessage in DMs + crew chat; Settings auto-translate off by default;
  memory-only translations; per-model download progress; 12 UI strings in all
  24 locales.
- Both model directions verified from the hosted files; tsc, 138 tests, build
  and smoke green.
2026-10-27 10:49:54 +07:00
SkateSpots
fd9316331b feat(trust): Settings -> Trust and name AGPL on the License page
- Settings -> Trust section (?section=trust, TrustSettingsCard): a
  show/hide-marks toggle (useTrustDisplay; Flagged is never hidden) and the
  active provider + relay (useTrustProvider), linked to /trust.
- TrustBadge respects the display preference.
- Name AGPL-3.0 on the License page by updating its existing copy
  (licenseLabel + body) in en/es.
- Settings + card i18n keys; /settings?section=trust added to the smoke
  list. Docs updated.
2026-10-09 17:45:56 +07:00
SkateSpots
b37d627138 Add a slim, dismissible site-links strip to the map
The desktop footer is hidden on full-bleed map routes, leaving the
Newsletter/License/Privacy/Collaborators links (and the "not sponsored by
OpenSats" note) unreachable from the homepage. MapFooterStrip overlays them
inside the map container without stealing map height:

- Desktop: a slim full-width bottom bar.
- Mobile: a tiny collapsed "Links" chip that expands to a compact, scrollable
  row.
- Dismissible (useMapFooterHidden, device-local, same-tab synced) and
  restorable from Settings -> Appearance.
- The map's OSM attribution moved up (md:bottom-7) so the bar doesn't cover it.
2026-10-05 09:15:51 +07:00
SkateSpots
4cad201716 Add a Settings → Pro & Support hub
One discoverable home for every paid extra, so the pricing model is legible
instead of scattered across the Sasquatch panel and each offer form:

- components/ProSupportScreen.tsx (Settings ?section=pro + a menu row): honest
  "prepaid, not a subscription" framing; Sasquatch Pro status (days left) +
  PRO_TIERS with per-day sats and a Go Pro / Extend button; Featured placement
  (PLATFORM_FEES x DURATION_TIERS with per-day + best-value); your currently
  featured Lab listings (featuredUntil); and the free paths (PoW listing, free
  AI FAQ).
- Distinct naming in copy: Sasquatch Pro (AI) vs Featured placement (visibility).
- Smoke test now walks /settings?section=pro.
2026-10-04 14:28:13 +07:00
SkateSpots
8cdc41cc15 Quieter Sasquatch bubble: no perpetual pulse, hideable, map-aware
Addresses "the floating Sasquatch flashing is annoying":

- Pulse is now state-driven: the animate-ping halo shows only when there's an
  unread tip (message), never perpetually; the unread dot matches.
- Honors prefers-reduced-motion (motion-reduce:hidden).
- Map-aware: Layout passes isFullBleed, so on the map the bubble runs compact
  (smaller, no glow, no pulse) instead of competing with map controls.
- Hideable: a ✕ on the FAB hides it (useSasquatchHidden, localStorage
  sasquatch-hidden, same-tab synced via an event), with a "Show Sasquatch
  assistant" toggle in Settings → Appearance to bring it back.
2026-10-04 14:20:46 +07:00
SkateSpots
0b796440f1 feat(i18n): Spanish batch 2 — nav/chrome, events/shops/tours, crews/DMs, settings/auth, wallet/stash/newsletter/stats
~3,470 new keys (5,770 total, EN=ES), ~115 more files wired.
- nav + chromeUi: header, More menu, burger menu, footer, map toolbar widgets
  (sort/trust/weather), spot-type label helper.
- placesUi: events, shops, tours, food. socialUi: crews, DMs, notifications,
  collectives, crew chat/coordinator. settingsUi: settings, auth/signup,
  onboarding, profile edit, legal. walletUi: wallet/Cashu, stash, mints,
  app shop, newsletter, community stats, leaderboards, profile.
- Fixed 738 usages where an agent dropped the socialUi. prefix (caught by check:i18n).
2026-10-02 10:11:06 +07:00
SkateSpots
dc0ae9bc32 feat(stats): admin backfill for legacy untagged spots/shops
Spots created before reverse geocoding have coordinates but no city/country
tag, so the map draws them while /stats can only show "Uncategorised". Counted
on the canonical relay: 54 raw -> 43 after admin-hide -> 13 tagged / 30
uncategorised, and 21 of the 30 are the two admin accounts' own pins.

useRegionBackfill finds the logged-in user's own untagged spots/shops, decodes
each pin's coords (lat/lon else geohash), reverse-geocodes via the shared
reverseGeocodeRegions(), and republishes the addressable event with the region
tags added - preserving every existing tag. Publishes to getWriteRelays()
(community + author) so the canonical stats see it, spaced 1.1s for Nominatim's
1 req/s policy. Surfaced as an admin-only RegionBackfillCard in Settings ->
Advanced.
2026-10-01 13:58:21 +07:00
SkateSpots
7b33d544b1 feat(coordinator): Cordn client slice — pick/add a coordinator + run-your-own hand-off
SkateSpots stays a Cordn *client*: it points at a coordinator the user
chooses or runs, and hands phone-hosting off to Cordn's own app. No embedded
coordinator, no default SkateSpots coordinator (deliberate: liability).

- src/lib/coordinator.ts: Cordn's own formats — an nprofile pointer, the
  canonical add-link https://cordn.net/chat/coordinators?c=<nprofile> (from
  Cordn packages/server/src/main.ts), default relay wss://relay.contextvm.org,
  the docker one-liner. Parses nprofile/npub/hex/link; encodes an add-link.
- src/hooks/useCoordinator.ts: device-local store + presence probe. Health is
  NOT Cordn RPC — it queries the coordinator's relays for authors:[pubkey] (its
  ContextVM announcement/heartbeat), so a private/unannounced coordinator
  honestly reads 'Not seen' rather than failing.
- src/components/CoordinatorSettingsCard.tsx + Settings -> Coordinator:
  empty/connected states, add by paste or QR scan, add-link copy + QR, and a
  'Run your own' hand-off (open Cordn, scan its QR, copy the docker command).
- smoke test now boots /settings?section=coordinator.
2026-09-29 15:11:36 +07:00
SkateSpots
665d875df1 ui(settings): give Edit Profile a real preview, honest reload, and a finish checklist
The profile screen was a bare 'view my public profile' link, a banner that
wrongly said 'Profile not loaded' whenever the user simply had no name yet,
a directory card, then the form.

- ProfilePreviewCard: banner + avatar + handle + bio + badges, so you can see
  how you look to everyone else without leaving the page. The public-profile
  link moves into it.
- The reload banner now only shows when we have NO metadata at all, and says so
  honestly instead of keying off a missing name.
- ProfileChecklist: a compact nudge listing only genuinely empty essentials
  (name, photo, bio, lightning address, handle); hidden once complete.
2026-09-29 14:27:20 +07:00
SkateSpots
63dc4bee7d feat(notify): opt-in on-device OS notifications (Phase 1)
Mirrors the activity the app already computes (bell, crews, DMs, newsletter)
to the OS notification shade, with no new relay traffic and no new network
destination.

- src/lib/osNotify.ts: one delivery API. Web uses the Notification API; native
  uses @capacitor/local-notifications (lazily imported, so the web bundle never
  pays for it). Tap-through navigates in-app.
- src/components/OsNotificationBridge.tsx: mounted once in Layout, inert until
  opted in. Per-source silent priming + a seen-id set means history is never
  replayed as a flood, and it stays quiet while the app is visible (the bell
  already covers that).
- DMs notify from the gift-wrap ENVELOPE only (src/hooks/useDmEnvelopes.ts,
  no decryption) — so Amber is never prompted just to be told you have a
  message. Preview is honestly generic.
- src/lib/notificationPrefs.ts + useNotificationPrefs: device-local, opt-in,
  default off, per-category toggles. Settings -> Notifications UI.

Deliberately local-only: a fully closed app needs a server push, which is not
built (Phase 2 = on-device background runner; Phase 3 = content-free FCM).
2026-09-29 12:59:13 +07:00
SkateSpots
124cac74fa fix(pro): stack Pro proofs + owner-grant fallback
#1 Extend now actually extends. useSasquatchPro collected receipts and took the
max of (receipt.created_at + tierDays), so buying again before expiry
under-delivered. It now stacks every valid proof with renewal semantics
(expiry = max(expiry, proofTime) + days), client and server alike.

#2 Owner-grant fallback. Pro can now also be granted by an owner-authored NIP-32
kind-1985 label (org.skatespots.pro, p = user, expiry) — so Pro is not solely
dependent on the Lightning provider publishing zap receipts. New owner-only
GrantProCard (Settings -> Advanced) publishes it to getWriteRelays(); the hook
reads via querySkateContent.

Protocol: the x-sasquatch-pro payload is now {receipts,grants}; BOTH ai-proxy and
the Netlify function accept the new payload and a bare receipt (old APKs). Proxy
was deployed first and verified healthy before this commit.
2026-09-28 18:58:25 +07:00
SkateSpots
b9b0c53e03 docs(privacy): tell users about the IP surfaces (mints, directions, AI) + surface it where it matters
- /privacy IP section now lists Cashu mints, directions (OSRM/Transitous receive
  the route endpoints), and Sasquatch AI — and states the sharp bit: a relay can
  link your IP to your pubkey ('this person, from this address').
- Corrects the old 'none of these receive your GPS location' claim, which stopped
  being true once directions shipped (they get your origin/destination).
- Notes the one we DO shield: the mints directory is proxied through our server,
  so MintRadar never sees the user's IP.
- Removes the named VPN brand; copy is now neutral ('use one you pay for and
  trust; treat free as a warning sign').
- Settings -> Relays gets a one-line privacy note linking to /privacy.
2026-09-28 18:23:43 +07:00
SkateSpots
3c7c8bd87e feat(nip05): free name@skate-spots.org handles, claimed at signup
SkateSpots now runs its own NIP-05 registry, so a new skater gets a human
handle from minute one instead of an npub.

- netlify/functions/nip05.mjs: /.well-known/nostr.json plus
  /api/nip05/{check,claim,release}, backed by Netlify Blobs (first-party,
  no new vendor). Claims are NIP-98 signed and written onlyIfNew, so only
  the owner can claim a name for themselves. Platform names are seeded in
  code and cannot be taken or released.
- src/lib/skateNip05.ts + src/hooks/useSkateNip05.ts +
  src/components/SkateNip05Input.tsx: shared validation, debounced
  availability, and the claim control.
- SignupDialog: a 'SkateSpots name' field, prefilled from the display
  name. Claimed best-effort before the profile publish; a failure never
  blocks signup, and the profile only advertises nip05 once confirmed.
- EditProfileForm: claim a free handle from the NIP-05 field.

Verified on a draft deploy: claim -> resolves -> a second key gets 409 ->
release -> gone. tsc/eslint/101 tests green.
2026-09-28 12:13:34 +07:00
SkateSpots
a43ff47b28 feat(spots): honest quality signals, photo required, off-topic reports, admin review queue
The 'test of secret island airport' problem: a junk pin wearing an app-endorsed
'Skateability 95/100'.

- Rename the weather score to 'Conditions · weather only' (SpotDetailPage +
  the SpotMap popup). It was never a verdict on the place.
- New Community verified / Unverified badge from signals only OTHER skaters can
  produce: community photos + useSpotStatus reporterCount (kind 2259).
- CreateSpotPage now REQUIRES at least one photo (guard + disabled submit +
  a required hint on the label).
- useNotASpotReport: one-tap NIP-56 (1984) report tagged 'not-a-skate-spot'
  (NIP-56 'other' + our L/l/t taxonomy), wired into the spot's More menu and
  added to the shared ReportDialog reasons.
- useAdminHide: the MISSING WRITE SIDE for admin moderation. useAdminModeration
  always read NIP-32 kind-1985 'hidden' labels but nothing published one, so the
  only working hide was device-local. Now publishes the label (+ optimistic
  local hide). Soft and reversible — the event stays on relays.
- /review (AdminReviewPage, admin-only → 404 otherwise): review queue over
  useSkateSpots with cheap flags (suspicious title, name <=3 chars, no photo, no
  description, no geohash), sorted by flag count, one-tap Hide or Not a skate
  spot. Entry via the admin-only AdminReviewCard in Settings → Advanced.
- /review added to the smoke-test route list.

Verified: the airport spot shows UNVERIFIED with 'Conditions · weather only' and
no 'Skateability'; the review queue lists it with flags + Hide/report actions;
create-spot blocks without a photo.
2026-09-27 23:41:49 +07:00
SkateSpots
62b395f3d2 feat(sasquatch): align client models with the node + owner AI-balance nudge
- sasquatchBrain: PROXY_MODELS now match the Routstr node's slugs
  (gemini-3-6-flash, z-ai-glm-5-3, gemini-3-7-flash).
- ai.mjs: new owner-only POST /api/ai/status (NIP-98 + owner pubkey) returning the
  upstream balance + a 'low' flag (<500 sats); config.path now serves both routes.
- useSasquatchAiStatus + SasquatchAiStatusCard (owner-only, in Settings): shows the
  balance and warns when low — the top-up nudge, no webhook/notification key needed.
2026-09-27 15:27:32 +07:00
SkateSpots
087f490acc refactor(layout): hoist Layout into the router so the chrome mounts once
- AppRouter wraps every route (except /amber-callback and the 404) in ONE
  <Route element={<Layout/>}> with an <Outlet/>; the chrome now persists across
  navigation instead of being torn down and rebuilt on every page.
- Layout: children optional (falls back to <Outlet/>); the map is full-bleed via
  a route check (isFullBleed) instead of a per-page noPadding prop.
- All 59 pages + CraftProfileForm/ErrorState/NewsletterIssueView now render
  fragments instead of <Layout> (structure-preserving), imports dropped.

Removes the whole remount class: chrome no longer re-runs its hooks per
navigation, Sasquatch chat state survives navigation, and the header can't
re-open Amber on every page change.
2026-09-27 13:41:42 +07:00
SkateSpots
9e8f8b87de refactor(settings): remove the Crew Network (nostr-vpn) screen
Poor fit for a skate app: it only funneled users to install a third-party VPN
app (QR join request + download links + platform detection) with zero in-app
function. Removed the sub-screen, its Settings menu row, the section enum entry,
the render switch, the nostr-vpn constants, and the now-unused imports.

Gate: 101 tests, 0 errors/warnings, boot smoke green.
2026-09-26 23:45:28 +07:00
SkateSpots
6def78fffe chore(lint): clear all no-unused-vars warnings (position-precise)
Removed/renamed the remaining 39 unused locals/dead code, file-by-file:
- unused destructured bindings/params (remove the binding, keep the call)
- unused single-line pure consts and dead functions/interfaces removed
- deleted the dead _FeedItem component and its now-unused imports
- amber _readChunks self-call fixed; SpotMap popup call site updated

Warnings: 182 -> 7 (only react-refresh/only-export-components remain).
no-explicit-any=0, exhaustive-deps=0, no-unused-vars=0.
Gate: 101 tests, 0 errors, boot smoke green. AGENTS.md baseline + guidance updated.
2026-09-26 22:20:21 +07:00
SkateSpots
d1f8870379 chore(lint): remove all explicit any + sweep dead imports
- Fix all 12 no-explicit-any (NPool, NostrEvent[], LabProfile, structural
  Cluster type, explicit SkateSpot cast) -> 0 remaining
- Remove unused imports/vars across 48 files (manual + a verified codemod for
  unused import specifiers only); tsc + tests + boot smoke all green
- Warning backlog: 182 -> 76 (remaining: ~55 no-unused-vars locals,
  14 react-hooks/exhaustive-deps, 7 react-refresh)
- AGENTS.md baseline updated
2026-09-26 21:40:42 +07:00
SkateSpots
09e1612db7 fix(a11y): Batch E part 1 — labels, valid nesting, touch targets
- EmptyState: render <Link> as the button (a <button> inside <Link> was invalid
  and double-focusable); AboutPage <a href> -> <Link> (no full reload)
- aria-label on icon-only controls: settings Back, conversation Refresh/Send,
  crew chat Send
- Crew invite Accept/Decline raised to 44px touch targets
2026-09-26 18:46:51 +07:00
SkateSpots
f4192e2f3d fix(ux): Batch A part 1 — dead flows
- SpotDetailPage: 'Claim This Shop' was a no-op button; wire to NIP-05 settings
  (and stop the full-page <a href> reload)
- TourDetailPage: 'Contact Guide to Book' used /messages?to= which nothing read,
  so booking silently opened an empty inbox -> /messages/:npub
- ContactsPage: row 'Message' went to the generic inbox -> the contact's thread
- SettingsPage: ?section=follows|language|stash rendered a blank dead end;
  unknown sections now fall back to the menu
- DirectMessages/Conversation: logged-out states were dead ends -> add LoginArea
- Conversation: failed sends were silent -> error toast
2026-09-26 17:43:19 +07:00
shakespeare.diy
5d96f8dfa8 Add tour countdown badges + fix/standardize NIP-05 provider prompts
Countdown badges (upcoming tours):
- New getCountdown() in TourCard: "In 3 days" / "Tomorrow" / "In 2 weeks" /
  "Happening now" (pulsing) based on start/end dates, category-tinted
- Shown on both compact and full TourCard, and in the TourDetailPage schedule card

NIP-05 provider fix (removes obsolete nip05.social):
- New single source of truth src/lib/nip05Providers.ts with working, curated
  providers: nostrcheck.me + nostr.directory (free), Nostr Plebs + nostr.band (paid)
- New reusable Nip05Required component (full-page "gate" + inline "banner"
  variants) listing those providers and deep-linking to /settings?section=nip05
- Wired into CreateTourPage (gate), CreateListingPage (banner) and all four
  Become* pages (Coach/Filmer/Photographer/Builder) for a consistent prompt
- SettingsPage NIP-05 screen now reads from the shared provider list
- Updated internal planning docs to match

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-09 22:31:44 +07:00
shakespeare.diy
67e422307a Citrine audit: endpoint fallback, a11y, reduced-motion, UX polish (1.3.6)
Full self-audit of the Citrine feature. Fixes found:

Robustness
- Probe now tries BOTH ws://127.0.0.1 and ws://localhost and routes to whichever
  actually answers (probeCitrineEndpoints + CitrineProbeResult.url). We no longer
  bet the whole feature on one loopback address form; useCitrine.url reflects the
  winning endpoint so reads/writes go to the right place.

Correctness
- Preview toggle was mutating the REAL setting while in dev-preview; handleToggle
  is now a no-op under preview (forced state owns the visuals).

Accessibility / "flawless" bar
- Respect prefers-reduced-motion: status pips, chip pop-in and the expand slide
  now motion-reduce:animate-none (no looping glow/pulse for reduced-motion users).
- Recheck button gets an aria-label; status text is role="status" aria-live so
  screen readers announce searching→detected transitions; decorative pips are
  aria-hidden.

UX copy
- "Waiting" state no longer presumes Citrine isn't installed. Clearer two-step
  guidance (Open Citrine → Start; come back → Recheck) covering the common
  "installed but not started/detected yet" case.

Honest note: MIXED_CONTENT_ALWAYS_ALLOW (1.3.5) is the high-probability fix for
the 0-connections block but is not device-verified from here; the dual-endpoint
probe hedges the second most likely cause (address form).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-09 10:54:22 +07:00
shakespeare.diy
c7e890e65c Citrine: allow loopback ws:// in WebView so the local relay connects (1.3.5)
The real fix for "Citrine running but 0 connections": the app is served over
https in the WebView and Citrine listens on plaintext ws://127.0.0.1, which
modern Android WebViews block as mixed content BEFORE it reaches the network
layer — allowMixedContent + the loopback network-security-config don't fully
cover WebSocket mixed content.

MainActivity now sets the bridge WebView's mixed-content mode to
MIXED_CONTENT_ALWAYS_ALLOW after super.onCreate(), which lets the localhost
ws:// connection through for every user. All real traffic stays wss:///https://;
this only unblocks the loopback relay. The WebViewClient is left untouched
(overriding it previously caused a black screen). Wrapped in try/catch so it can
never crash the launcher.

Also removed the temporary on-screen probe debug readout (kept the diagnostic
fields in useCitrine, unused by UI, for future use).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-09 10:47:39 +07:00
shakespeare.diy
97adfba7e0 Citrine: on-device probe diagnostics (no USB needed) (1.3.4)
Citrine is running (listening on ws://127.0.0.1:4869) but the app can't connect
and shows 0 connections — a WebView-layer block, not our logic. To diagnose
without adb/USB, the probe now returns { ok, reason } capturing which stage
failed (blocked / connect-error / timeout-no-open / opened-but-no-nostr-reply /
closed-before-open with code), and the Settings card shows a small debug readout
(probe url + reason) whenever not connected. Purely additive; no behaviour
change to routing.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-09 10:44:38 +07:00
shakespeare.diy
335fb40f72 Citrine: add safe dev-preview flag to review the UI in a browser (1.3.2)
The CitrineCard is Android-only (renders null off-native), so its four visual
states couldn't be reviewed without an APK build. Adds a strictly cosmetic
preview switch:

  localStorage.setItem('citrine:preview', '1')         // show card, real behaviour
  localStorage.setItem('citrine:preview', 'live')      // force connected state
  localStorage.setItem('citrine:preview', 'waiting')   // force not-detected
  localStorage.setItem('citrine:preview', 'searching') // force searching
  localStorage.removeItem('citrine:preview')           // back to normal

Safety: the flag unlocks RENDERING ONLY via isCitrinePossible(). It never sets
citrine.active and never yields a relay url, so no query or publish is ever
routed anywhere different — production relay behaviour is identical with or
without the flag. A forced state is purely visual (enabled/probing are derived
locally in the card for preview; the real probe still governs active/url).

lib/citrine.ts: citrinePreview()/isCitrinePreview() + CitrinePreviewState.
useCitrine: exposes `preview`. CitrineCard: forced state overrides the computed
state for review only.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-08 19:18:51 +07:00
shakespeare.diy
2e6a20dc2b Citrine: native cleartext config, default-pool write wiring, polished UI (1.3.1)
Completes the local-relay integration so it actually works end-to-end and feels
first-class.

Native (Android):
- android-templates/network_security_config.xml — permits cleartext for
  LOOPBACK ONLY (127.0.0.1 / localhost / ::1); all real traffic stays TLS. This
  is what lets ws://127.0.0.1 (Citrine) connect on API 28+, which otherwise
  silently blocks cleartext.
- scripts/inject-network-security.mjs — idempotently copies the XML into
  res/xml/ and adds android:networkSecurityConfig to <application>, mirroring
  the existing inject-amber-queries pattern so it survives `cap sync`.
- Wired into android:init / android:sync / android:build.

Reads AND writes now reach Citrine app-wide:
- NostrProvider's NPool routers (reqRouter/eventRouter) fold in the active
  Citrine URL via a ref, so every default-pool query hits it first (instant
  local reads) and every useNostrPublish write also lands on-device (backup +
  offline capture). Previously only explicit useSkateRelays paths benefited.
- A dedicated effect refreshes ['nostr'] queries when the local relay comes/goes
  so reads switch over immediately. Fully no-op off-Android (url stays null).

UI/UX (Relays settings CitrineCard):
- Four crisp states — off / searching / live / waiting — with a breathing then
  glowing status pip, benefit chips (Instant reads · Works offline · On-device
  backup) that pop in on connect, a spinning Recheck, an actionable install CTA,
  and a persistent encryption-reassurance note.
- Graceful first-run: shows "searching" until the first probe settles (via new
  useCitrine.settled), never flashing the orange "not detected" state.

Tailwind: added status-breathe / status-live / pop-in keyframes+animations.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-08 19:11:51 +07:00
shakespeare.diy
bd9b71095d Citrine: optional local on-device relay for instant reads + offline backup (1.3.0)
Integrates Citrine (github.com/greenart7c3/Citrine), an Android app that runs a
full Nostr relay locally on the phone. Because it's "just a relay URL", it folds
into the existing useSkateRelays / nostr.relay() machinery with no new protocol.

What it buys SkateSpots:
- Instant reads: a localhost hit (~1ms) is queried first, remote relays run in
  parallel and reconcile — every skate-content surface feels faster.
- Offline-in-the-field: reads work with no signal; writes land on-device and
  publish to remote when connectivity returns.
- Resilience/backup: a durable local copy of the user's own events survives
  public-relay outages and addressable-event eviction.
- Privacy: local-first data can lean on the device instead of public relays.

Design / safety:
- Android-only (Capacitor native + platform === 'android'); renders nothing and
  never probes localhost on iOS/desktop web. Mixed-content ws:// is already
  permitted via capacitor allowMixedContent.
- Opt-in only via config.useCitrineRelay (default off). No data is ever routed
  through Citrine unless the user enables it AND a relay is actually reachable.
- Reachability probe (short-lived WebSocket) cached through react-query, shared
  app-wide, re-checked periodically + on focus, with a manual Recheck button.
- When active, the local URL is prepended to reads and included in writes; the
  read-relay set feeds getQueryKeySuffix so caches refetch automatically when
  Citrine comes/goes.
- Encryption unchanged: Stash/DMs stay NIP-44 encrypted regardless of
  destination — the Settings card states this explicitly. Local ≠ plaintext.

New: src/lib/citrine.ts, src/hooks/useCitrine.ts, CitrineCard in the Relays
settings screen. AppConfig gains useCitrineRelay (zod-validated, optional).

On-device verification still needed: confirm Android permits ws://127.0.0.1
cleartext to localhost (network security config) once Citrine is running.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-08 18:53:16 +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
1528c7b343 Unify the two "Profiles": public page + Edit, cross-linked and clearly named
The app had two things both called "Profile": the /settings editor and the
public /npub page. They're not duplicates (edit vs. view) but the naming and
lack of links between them was confusing. Now:

- Public profile (/npub) gets an owner-only "Edit Profile" button that deep-
  links to /settings?section=profile
- Settings row renamed "Profile" -> "Edit Profile" (section title already said
  Edit Profile); it already has a "View my public profile" link back
- Result: one Profile (the public page), one obvious way to edit it, linked
  both directions — the standard IG/GitHub pattern

Bonus fix: mobile menu "My Data" used a broken ?tab=mydata param; corrected to
?section=mydata (the settings page reads ?section=).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 21:44:05 +07:00
shakespeare.diy
ee9e04d3e0 Profile access, real gig messaging, desktop Local Chat removal + audit fixes
#0 Crew band gig booking:
- Rename "Book them for a gig" -> "Set up a gig" (honest: it creates an event)
- Add "Message about a gig" -> pre-filled encrypted DM (NIP-17) to the crew
  founder via existing /messages/:npub?message= flow (real communication)

#1 Profile access (was missing/mislabeled everywhere):
- Desktop header: add "View my profile" icon linking to /npub
- Mobile user menu: split mislabeled "My Profile"(->Settings) into
  "View Profile" (-> /npub) + "Settings"
- Burger menu: add "View Profile" entry
- Settings > Edit Profile: add "View my public profile" quick link

#2 Remove standalone Local Chat button on desktop map toolbar (kept on mobile)

Audit fixes:
- Mobile menu "Wallet & Stash" -> "Wallet" (Stash is already its own item)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 21:16:15 +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
bb6b3e6b7e Fix map search input visibility; align header sizes; de-dupe map Quick Actions
- Map search input: replaced non-standard `bg-white/6` with `bg-white/5`
  (matching The Lab's inputs) and removed the focus ring offset so typed
  text is now visible on the dark toolbar instead of a white field. Also
  normalized the mobile filter button's `bg-white/6` -> `bg-white/5`.
- Bumped Wallet and Settings page titles to text-4xl md:text-5xl to match
  The Lab and Events headers for consistent title rhythm.
- Trimmed the map filter drawer's Quick Actions to Marketplace + Stash,
  since Add Spot / Shop / Tour / Quick Snap now live in the Add speed-dial
  FAB (removes duplication).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 10:24:52 +07:00
shakespeare.diy
05c7b76d6d fix(crew-network): comprehensive UX/accessibility overhaul
QR rendering:
- Fixed layout shift (CLS) — canvas and spinner now occupy the same fixed
  236×236 container; spinner fades out, canvas fades in, no height jump
- Added error state with Retry button when QRCode.toCanvas() throws
- Canvas now has role="img" + descriptive aria-label for screen readers
- "Save QR" button exports canvas → PNG via toDataURL (mobile share flow)

Deep link / "Open in app":
- Replaced document.createElement('a').click() with window.location.href
  (the only reliable way to fire custom schemes on iOS Safari PWA)
- Added 2 s fallback timeout: if the page doesn't lose focus (app not
  installed), automatically redirects to App Store / GitHub releases
- Button shows "Opening…" state while the timeout is pending
- Added hint text explaining the fallback behaviour

Platform detection:
- detectPlatform() reads navigator.userAgent to identify iOS/Android/desktop
- Download list is sorted so the most-relevant entry appears first
- Each download row now has a sublabel (e.g. "iPhone & iPad", "arm64 devices")
- Added Android APK entry (was missing)

Accessibility:
- All interactive buttons have focus-visible:ring-2 keyboard focus rings
- Decorative icons marked aria-hidden="true" throughout
- "How it works" list changed from <div> to <ol>/<li> with aria-label
- Download list uses role="list" / role="listitem"
- Use-cases grid uses <ul>/<li> with aria-label
- "Learn more" link has full descriptive aria-label inc. "(opens in new tab)"
- Action buttons have aria-label for screen readers

Contrast & copy:
- URI preview: text-muted-foreground/60 → text-white/70 (passes WCAG AA)
- URI box is now select-all / cursor-text for easy manual copy
- Hero icon changed from Network → Shield (semantically: encrypted/private)
- Step 2 text adapts when logged out ("Log in to generate your QR")
- Use-case emoji changed 🎬→🔒 for "Private crew comms" (more on-message)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 22:23:20 +07:00
shakespeare.diy
fb6c1bca36 feat: add Crew Network section to Settings with nostr-vpn deep link integration
- New "Crew Network" menu item in Settings > App, positioned below Relays
- Full CrewNetworkScreen sub-screen with:
  - Hero explainer card (nostr-vpn private mesh VPN concept)
  - Live QR code generated via `qrcode` canvas for the join-request URI
    (nvpn://join-request?pubkey=<hex>&name=<displayName>&relay=...)
  - Copy link button + "Open in app" deep link button
  - Step-by-step "How It Works" list
  - Download links for iOS App Store, TestFlight beta, and GitHub releases
  - 2x2 use-case grid (private crew comms, secret spots, censorship bypass, offline mesh)
  - GitHub "Learn more" row
- Logged-out state shows a LoginArea prompt
- TypeScript Section type extended with 'crewnetwork'
- Cleaned up unused Palette/Settings lucide imports

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 22:16:53 +07:00
shakespeare.diy
dd85644fca UX audit fixes: iOS directions, Me tab profile link, onboarding banner, empty states, stats deltas, newsletter pill
- Fix iOS directions: Apple Maps first on Apple platforms, removed broken geo: URI,
  added Waze option, fixed popover z-index clipping behind Sheet component
- Fix 'Me' bottom nav: now deep-links to /settings?section=profile for logged-in users
  so tapping 'Me' shows your profile, not a generic settings menu
- Add SettingsPage URL param support: ?section=X opens sub-screen directly
- Add onboarding welcome banner: dismissible strip at top of map for logged-out users
  with 'Get Started' CTA pointing to /onboarding, stored in localStorage
- Fix Lab empty state: FAQ link + login prompt for logged-out users
- Fix Events empty state: FAQ link added to all empty state instances
- Fix GearSwap empty state: actionable 'Clear Filters' button + FAQ link
- Community stats: add 'this month' delta badges (+N this month) on all stat cards
- Newsletter pill: hide on map page to eliminate bottom sheet overlap conflict

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 21:36:33 +07:00
shakespeare.diy
1ce2837fef settings: replace 7-tab horizontal scroll with iOS-style vertical menu + sub-screens
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 19:32:38 +07:00
shakespeare.diy
0dc0d272e1 fix: filter panel overlap, Wallet & Stash rename, Sasquatch on all pages
Filter panel (MapPage):
- Sheet now h-[82vh] max-h-[680px] with flex-col layout
- Scrollable content area uses flex-1 min-h-0 (proper flex children)
- "Show Results" button is now flex-shrink-0 at bottom of column
- No more overlap between Show Results and Stash/Tour quick action buttons

Wallet & Stash rename:
- Settings tab renamed from "Wallet" to "Wallet & Stash"
- "Me" dropdown in mobile header shows "Wallet & Stash" for the wallet link

Sasquatch persistent button (all pages):
- Minimized bubble is now ALWAYS visible on every page (not just pages with a guide message)
- Tapping the bubble on any page opens Sasquatch with a generic helpful greeting
- Pages with specific context (/, /map, /lab, /onboarding) still show their custom message
- Pages without context (/wallet, /settings, /stash, /events, etc.) now show a generic "Ask me anything" greeting with common questions
- Removed the separate X close button (minimize was already the same) — bubble stays visible as entry point at all times
- onPointerUp now also sets isVisible=true so panel opens on pages without a message

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 16:45:11 +07:00
shakespeare.diy
340f6fd89f fix(mobile): redesign wallet page UI for mobile + global mobile polish
Wallet page (/wallet):
- Add BalanceHero banner at top showing Cashu balance + wallet status pills
- Cashu/Lightning tabs with colored active states (orange/yellow)
- Remove nested balance display in CashuWalletCard (now shown in hero)
- Compact balance row in CashuWalletCard
- Stacked full-width inputs on mobile (no side-by-side on small screens)
- h-11 touch targets on all inputs and buttons (44px min)
- inputMode="numeric" on number fields (prevents iOS zoom)
- Receive/Send tabs with green/red active colors for clear context
- History items now 52px min height with colored amount text
- WalletComparisonGuide stays collapsed by default
- Fix: add Nut icon import to CashuWalletCard (was crashing)
- All modals use w-[calc(100%-1rem)] for proper mobile sizing

Settings page (/settings):
- 7-tab bar now scrollable horizontally on mobile (no overflow/wrapping)
- -mx-3 bleed trick so scrollable area goes edge-to-edge
- Wallet tab buttons full-width on mobile
- Container max-w-3xl for better desktop centering

FAQ page (/faq):
- Compact hero on mobile (smaller icon, tighter padding)
- py-6 on mobile instead of py-12

CSS utilities:
- Add xs: breakpoint (375px) variants: flex, flex-row, px-4, -mx-4, gap-2, text-xs/sm, hidden

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 16:35:49 +07:00
shakespeare.diy
6331a937f2 feat: map as homepage, 3D globe, camera fix, Sasquatch upgrades, UI/UX polish
## 1. Map as Homepage
- `/` now renders MapPage (the globe/map) — removed old Index homepage
- `/home` redirects to Index (legacy), `/map` alias still works
- Navbar: removed HOME link, SPOTS (MapPin) is now first nav item
- isActive() treats both `/` and `/map` as active for SPOTS tab

## 2. 3D Globe View
- New SkateGlobe component using pure Three.js (no h3-js/CDN issues)
- Earth night texture with atmospheric neon red glow
- Spot pins (red) + shop pins (lime) on globe surface
- Click pin → zoom camera in + show info card
- Drag to rotate, scroll/pinch to zoom, auto-rotation when idle
- Globe toggle button added to map/list view switcher
- Selected marker ring + info card with View Spot/Shop links
- Lazy-loaded with Suspense skeleton

## 3. Settings → Stash Link
- Added My Stash card in Settings > Wallet tab
- Links to /stash with Package icon and description
- Smooth hover animation with ChevronRight

## 4. Camera Fix (Capacitor Native)
- CameraCapture now detects native Capacitor platform at runtime
- On native Android/iOS: uses @capacitor/camera (OS-level camera, no WebView permission popup)
- On browser: falls back to getUserMedia stream (existing behaviour)
- Added Upload from Gallery fallback when camera permission denied
- Better error handling for user cancellation

## 5. Sasquatch Upgrades
- FAQ page: replaced static accordion wall with Sasquatch-powered UI
  - Quick question chips that pre-load questions into Sasquatch
  - "Open The Sasquatch" CTA button
  - Quick facts grid, clean modern design
- About page: added Sasquatch CTA card at bottom
- SasquatchGuide: listens for sasquatch:open and sasquatch:ask custom events
  - External pages can now trigger and pre-fill the Sasquatch
  - Sasquatch now shows even without a page-level message prop (for external triggers)

## 6. UI/UX Audit & Polish
- Focus rings: on-brand red (WCAG accessible)
- Card hover micro-interactions: subtle translateY + shadow
- Button/link transition consistency (150ms ease)
- Reduced motion support (@media prefers-reduced-motion)
- Modal backdrop blur improvement
- Skeleton shimmer animation utility
- Globe CSS utilities (touch-action: none for canvas)
- Typography helpers (text-balance, text-pretty)
- Improved map-full-height responsive calculation

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-22 22:47:39 +07:00
shakespeare.diy
dd650ee57c Show stored pubkey in Settings when profile not loaded (debug)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-09 14:42:47 +07:00
shakespeare.diy
c2b7a29d07 feat: update default relay set to all 5 relays
Added auth.nostr1.com and wotr.relatr.xyz back as default relays.
All 5 relays are now the default for all new users:
- wss://auth.nostr1.com (auth)
- wss://wotr.relatr.xyz (web of trust)
- wss://relay.ditto.pub (general)
- wss://goskatespots.nostr1.com (community - toggleable)
- wss://purplepag.es (profiles - read only)

Updated across: App.tsx, NostrProvider.tsx, NostrSync.tsx, TestApp.tsx
Updated Settings page Default Relays info cards to show all 5 with descriptions.

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

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 21:49:02 +07:00
shakespeare.diy
9d1e875c36 fix: remove wss://wotr.relatr.xyz relay that was consistently failing
The wotr.relatr.xyz relay was generating a flood of AbortError warnings
on every query because it was consistently unreachable/timing out.

Removed from:
- src/App.tsx (default config)
- src/components/NostrProvider.tsx (DEFAULT_RELAYS)
- src/components/NostrSync.tsx (NEW_USER_RELAYS for new users)
- src/test/TestApp.tsx (test config)

Replaced the Settings page entry with purplepag.es (profile aggregator)
which is already in the relay list and actually working.

Updated Sasquatch FAQ answer to reflect the correct relay list.

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

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

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

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 22:18:28 +07:00
Renamed from src/src/pages/SettingsPage.tsx (Browse further)