Commit graph

663 commits

Author SHA1 Message Date
shakespeare.diy
61795db997 fix: implement NIP-46 nostrconnect:// Amber login properly
- Fix useLoginActions.ts: remove generateNostrConnectParams, generateNostrConnectURI,
  NostrConnectParams, NostrConnectStatus which don't exist in @nostrify/react@0.2.17/login
- Rewrite LoginDialog.tsx Amber tab with proper NIP-46 client-initiated flow:
  - Generates ephemeral client keypair + random secret
  - Builds nostrconnect:// URI (no nostrsigner: scheme, no intents)
  - Opens Amber via window.open(_blank) - standard URI scheme Amber handles natively
  - Listens on relay pool for Amber's kind 24133 connect response
  - Decrypts with NIP-04, verifies secret to prevent spoofing
  - Uses NConnectSigner to call get_public_key
  - Creates NLogin bunker object and stores it persistently
- Bunker tab: direct NLogin.fromBunker() paste flow
- Extension tab: NIP-07 browser extension
- No clipboard hacks, no nostrsigner: scheme, no Android intent issues

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-19 00:02:21 +07:00
shakespeare.diy
916f067238 fix: rewrite login to use NIP-46 nostrconnect flow (Ditto pattern)
Completely replaces the broken NIP-55 intent approach with NIP-46:

- Amber tab: generates nostrconnect:// URI, opens Amber, waits for
  Amber to connect back via relay using NLogin.fromNostrConnect()
  (same pattern as Ditto/soapbox-pub)
- Bunker tab: paste bunker:// URI from Amber or nsec.app
- Extension tab: NIP-07 browser extension (desktop)
- useLoginActions: updated to match Ditto's implementation with
  nostrconnect(), generateNostrConnectParams, generateNostrConnectURI
- Clean mobile-first UI — no clipboard hacks, no intent tricks

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 23:49:55 +07:00
shakespeare.diy
e8bc072787 fix: replace NIP-55 Amber login with NIP-46 bunker flow
NIP-55 (nostrsigner: intents) is fundamentally broken from Capacitor
WebView — no combination of window.open, window.location.href, or
anchor clicks works reliably across all Android WebView versions.

Switch to NIP-46 (bunker://) which works everywhere via WebSockets:
- User opens Amber → Connect apps → Add app → copies bunker:// URI
- Pastes it into the login dialog
- NLogin.fromBunker() completes the handshake over a relay
- Login works in browser, PWA, and Capacitor APK identically

This is exactly how Ditto (soapbox-pub) implements Amber login.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 23:33:42 +07:00
shakespeare.diy
00d9b69f81 fix: use window.open(_blank) to trigger Amber's web/browser intent path
Amber has two code paths:
- Native path (no Browser.EXTRA_APPLICATION_ID): reads type from intent
  EXTRAS — impossible to set from a WebView URL navigation
- Web/browser path (Browser.EXTRA_APPLICATION_ID set): reads type from
  URL query params — triggered when WebView opens URL as browser intent

window.open(url, '_blank') causes Android WebView to fire a browser
ACTION_VIEW intent which sets Browser.EXTRA_APPLICATION_ID, routing
Amber to parse our nostrsigner: URL query params correctly.

Previous attempts:
- window.location.href: WebView intercepts as asset URL
- window.open(_system): strips query params  
- anchor click: goes through native path (no extras = INVALID type)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 23:18:02 +07:00
shakespeare.diy
f534c413fd fix: use anchor click to open nostrsigner: URLs in Capacitor WebView
window.location.href causes WebView to try loading nostrsigner: as an
asset URL. window.open(_system) strips query params. The only reliable
method in Capacitor WebView is creating a temporary <a> element and
clicking it — this correctly triggers Android's intent system with the
full URL intact.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 23:10:13 +07:00
shakespeare.diy
fdebfc0d37 fix: use window.location.href for nostrsigner: URLs instead of window.open(_system)
window.open(_system) was stripping query parameters from the nostrsigner:
URL, causing Amber to receive a request with no type parameter and
rejecting it as malformed.

window.location.href works correctly in Capacitor WebView — Android
intercepts nostrsigner: scheme via intent-filter with full URL intact.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 23:02:33 +07:00
shakespeare.diy
55856e2935 fix: use clipboard mode for Amber login in Capacitor APK
Capacitor native apps fail with "malformed nostrsigner request" when
using callbackUrl because Amber treats custom URI schemes as invalid.

Fix: detect window.Capacitor.isNativePlatform() and use the no-callback
clipboard flow (openAmberForLoginNoCallback) instead of the callbackUrl
flow. Amber copies the pubkey to clipboard, app reads it on
visibilitychange when user returns.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 22:56:28 +07:00
shakespeare.diy
3f654f4a35 fix: single bundle APK build, remove debug banner, clean build scripts
- Remove debug red banner from index.html and main.tsx
- Add android:build:apk script that uses CAPACITOR_BUILD=1 for single
  bundle output + strips Shakespeare build artifacts from dist/index.html
- Update all android:* scripts to use android:build:apk

Root cause of white screen: 100+ JS chunk files overwhelm Android
WebView local file serving. Single bundle with inlineDynamicImports
fixes this.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 22:51:49 +07:00
shakespeare.diy
fbdd0d7fbf fix: single bundle mode for Capacitor APK builds
Use CAPACITOR_BUILD=1 env var to disable code splitting and produce a
single JS bundle. This fixes the white screen caused by 100+ chunk
file requests failing silently in Android WebView local file serving.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 22:49:37 +07:00
shakespeare.diy
379a5f7b34 debug: add visible HTML marker to diagnose white screen in APK
Red banner renders from pure HTML before any JS loads.
If visible → HTML loads but JS fails.
If not visible → WebView can't even load index.html.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 22:32:18 +07:00
shakespeare.diy
be70ff490f fix: remove @capacitor/app dynamic import from DeepLinkHandler
Use window.Capacitor.Plugins.App directly instead of dynamic import()
so the build doesn't fail when @capacitor/app isn't in node_modules

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 22:01:40 +07:00
shakespeare.diy
c3ef0247fc fix: switch to HashRouter for Capacitor Android compatibility
The primary cause of the white screen in the APK was BrowserRouter.
Capacitor serves files via https://localhost/ and the HTML5 History API
routing (BrowserRouter) fails to resolve routes in this context,
resulting in a blank white page.

Changes:
- Switch BrowserRouter → HashRouter in AppRouter.tsx
- Add DeepLinkHandler component (handles appUrlOpen for Amber callbacks
  and other deep links in native context)
- Fix Amber callback URL builder to use org.skatespots.app:// custom
  scheme when running in Capacitor (so Android fires an intent back into
  the app instead of navigating a browser)
- Fix getSearchParams() in amber.ts to extract query params from hash
  fragment (HashRouter URLs look like /#/amber-callback?event=XXX)
- Fix captureAmberCallback in main.tsx to check hash fragment too
- Remove debug error overlay (no longer needed)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 21:56:23 +07:00
shakespeare.diy
8c25110f44 debug: add visible error overlay in APK to diagnose white screen crash
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 21:41:32 +07:00
shakespeare.diy
dc66ac993f fix: use global Capacitor object to hide splash screen (no package import)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 21:30:29 +07:00
shakespeare.diy
4b61b14d36 fix: splash screen + ErrorBoundary dark bg for Capacitor APK white screen
- Add SplashScreen.hide() call in main.tsx so splash dismisses when JS loads
- Set launchShowDuration: 0 + launchAutoHide: true in capacitor.config.ts
- Fix ErrorBoundary to use bg-black text-white instead of bg-background
  (bg-background = white before dark class is applied = white screen on error)
- These three combined should eliminate the white screen in the APK

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 21:27:34 +07:00
shakespeare.diy
26185c617a fix: remove CSP meta tag - was causing white screen in Capacitor APK
The CSP meta tag blocks Capacitor's internal asset loading (https://localhost
scheme) causing a white screen. Android/iOS provide OS-level sandboxing so
the CSP is not needed for security in native APK context.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 21:24:02 +07:00
shakespeare.diy
6a059a067b fix: relax CSP for Capacitor WebView - white screen fix
The strict CSP was blocking Capacitor's internal capacitor:// and ionic://
schemes causing a white screen in the Android APK. Added capacitor: and
ionic: to all relevant CSP directives (default-src, script-src, connect-src,
img-src, media-src, font-src).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 21:16:39 +07:00
shakespeare.diy
a677e8687d fix: Amber Capacitor APK + mobile UI improvements
Amber login (critical):
- Replace window.location.href with window.open(_system) in Capacitor WebViews
- Capacitor's WebView throws "malformed nostrsigner request" on href navigation
- New openAmberUrl() helper detects Capacitor via window.Capacitor.isNativePlatform()
- Add openAmberForLoginNoCallback() for PWA/Capacitor clipboard flow
- Fix all 6 Amber URL opens in amber.ts + LoginDialog.tsx

Mobile nav:
- Newsletter and Sasquatch AI (/pyramid) added to hamburger menu
- Sasquatch floating guide now shows on mobile (removed hidden md:block)

Lab page mobile:
- Action buttons (Offer Coaching/Filming/Editing/Get Card) now 2-col grid on mobile
- Service type filter (Coaches/Filmers/Editors) now 3-col grid, full width
- Reduced top padding on mobile (py-6 → py-12 on md+)
- Text truncates cleanly, icons don't overflow

Wallet page mobile:
- Reduced padding (py-6 md:py-8)
- Tab triggers use h-auto + py-2.5 to avoid overflow
- Smaller header text on mobile

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 20:48:11 +07:00
shakespeare.diy
576df73a6b fix: restore original SkateSpots logo (icon-1024.png) everywhere
Replace all icon.svg references with icon-1024.png across:
- index.html (favicon, apple-touch-icon, og:image, twitter:image)
- src/components/Layout.tsx (desktop header + mobile header)
- src/pages/Index.tsx (footer)
- public/manifest.webmanifest (PWA icon entry)

Also fix og:url and og:image/twitter:image to use correct domain
skate-spots.org instead of skate-spots.com

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 20:02:27 +07:00
shakespeare.diy
a91a6fcdf3 feat: update zapstore.yaml for local publish workflow with nostr git
- repository now uses the NIP-34 naddr for the Nostr git repo on nostrhub
- release_source points to local APK built by `npm run android:build`
- metadata_sources uses playstore only (no github dependency)
- removed GitHub Actions zapstore.yml workflow (not needed)
- added clear publish instructions in comments

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 19:57:07 +07:00
shakespeare.diy
d7985b219e feat: add zsp Zapstore publish config and CI workflow
zapstore.yaml:
- Rewritten to use correct zsp fields (repository, tags, metadata_sources)
- Uses GitHub releases as APK source (auto-detected by zsp)
- Removed non-standard fields (pubkey, categories → tags)
- Added supported_nips list

.github/workflows/zapstore.yml:
- Triggers on version tags (v*.*.*)
- Builds web app with Node 22
- Syncs Capacitor Android project
- Builds signed release APK via Gradle
- Creates GitHub Release with APK attached
- Installs zsp and publishes to Zapstore relay

Required GitHub secrets:
  KEYSTORE_BASE64   – base64-encoded .jks keystore
  KEYSTORE_PASSWORD – keystore store password
  KEY_ALIAS         – key alias in keystore
  KEY_PASSWORD      – key password
  SIGN_WITH         – nsec or bunker:// for Nostr signing

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 19:54:39 +07:00
shakespeare.diy
72cb690e64 fix: increase relay query timeouts and fix AppConfigSchema for cold mobile loads
- Raise QUERY_TIMEOUT in useSkateSpots from 8s to 15s
- Raise AbortSignal.timeout in useLabProfiles and useSkateEvents from 10s to 15s
- Add useSkateRelay to AppConfigSchema so it's preserved when reading from localStorage

On a fresh phone (no warm WebSocket, no TanStack Query cache), the app
needs to: load JS bundle, boot React providers, open a WebSocket to
goskatespots.nostr1.com, and receive EOSE — all before the timeout.
On slow mobile connections this easily exceeded the old 8-10s limits,
causing /home to show empty sections while /map worked (the connection
was already warm by then). 15s gives cold connections enough headroom.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 15:28:26 +07:00
shakespeare.diy
9ed2c12de1 fix: remove sponsor mention + add Nostr preview modal before publishing
- "Sponsors see the organic number" -> "Users can now see both numbers"
- PublishToNostrButton now opens a preview modal first (NostrPreviewModal):
  - Mock Nostr post card: avatar, display name, timestamp, post body
  - nostrText rendered with hashtags in blue and URL underlined
  - Fake engagement bar (replies/reposts/zaps at 0) for visual context
  - Character count shown below the card
  - Two actions: "Edit first" (closes modal) / "Publish" (confirms)
  - Full-width on mobile, correct safe-area handling via Dialog component
  - isPending state flows through so button shows "Publishing..." while signing

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 15:12:37 +07:00
shakespeare.diy
e4a9b454a4 fix: rename issue -> drop, fix hashtags, add 6 tags
- "Issue" replaced with "Drop" everywhere in UI and copy:
  Drop #001, Weekly Drops, Write Drop #002, next drop, etc.
- nostrText: hashtags consolidated to one line (some clients break
  each hashtag onto its own line when separated by newlines)
- nostrText: removed "Full issue:" prefix — bare URL unfurls cleaner
- 6 hashtags added: #skateboarding #nostr #skatespots #spotr
  #newsletter #skatespotsnews
- Publish button tags updated to include all 6 as t-tags
- Template comment updated to match new format

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 15:09:32 +07:00
shakespeare.diy
b5edb0ab52 fix: nostrText format — short teaser, URL as the star
Kind 1 notes render differently on every client (Damus, Primal, Amethyst,
Snort, etc). The fix: keep the kind 1 as a 5-line teaser that works as
plain text on any client, with the full formatted issue at the URL.

Issue #001 nostrText trimmed from 14 lines to 4.
Added template comment with rules for all future issues:
- Max 5 content lines before the URL
- No bullet lists
- URL always on its own line
- 3 hashtags max

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 15:00:26 +07:00
shakespeare.diy
4f7b741d77 fix: newsletter mobile layout — full phone support
- Page container: py-8 on mobile + pb-24 so content clears fixed bottom nav
- Masthead headline: single line (no br), text-4xl mobile / text-6xl sm+
- Issue header: vertical stack on all screens — date/badge row, large number,
  vibe text, then publish button full-width on mobile / auto on sm+
- Stats grid: always 3 cols (clean 2-row layout on phone), larger text-2xl
  numbers, text-[10px] labels readable on all Androids, shorter relay label
- Floating pill: max-w-[calc(100vw-1.5rem)] prevents overflow on narrow screens,
  right-3 tighter margin, whitespace-nowrap on text, touch-manipulation + 
  active:scale-95 for native tap feel, correct bottom offset for iOS safe area
- Monday admin banner: unchanged, already stacks correctly

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 14:56:30 +07:00
shakespeare.diy
248f5b5ff2 feat: newsletter v2 — structured issues, live stats, Nostr publish button
Structured issue schema (NewsletterIssue):
- updates[]: typed update items with icon/title/tag/body
- spotOfTheWeek?: optional typed spot block
- communityNote: free-text community section
- nostrText: plain-text ready to post as kind 1

Live community stats block (LiveStatsBlock):
- Pulls from useGrowthStats on every page load
- Shows spots, shops, events, mappers, coaches, tours
- Trusted/active account counts shown below spots + mappers
- "Live · pulled from goskatespots.nostr1.com" attribution

Publish to Nostr button (PublishToNostrButton):
- Only visible to 2 admin pubkeys — nobody else sees it
- Publishes kind 1 with nostrText + tags (skateboarding, skatespots, nostr)
- Includes relay URL tag pointing to /newsletter
- One-shot: button becomes "Published to Nostr" checkmark after success
- Full error handling with toast

Issue template now enforces consistent sections every week:
1. Community by the Numbers (live stats)
2. What's New (typed update cards)
3. Spot of the Week (optional — shows placeholder when omitted)
4. Community note
5. Support / Geyser

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 14:45:59 +07:00
shakespeare.diy
c146d94b9f feat: weekly newsletter system with Issue #001
- NewsletterPage (/newsletter) — zine aesthetic, dark bg, neon accents
  Issue #001 covers all 8 security measures shipped this week:
  geohash validation, URL sanitization, content limits, WoT defaults,
  trusted stats, OfficialBadge, marker clustering, DM phishing warning

- NewsletterOverlay — two overlays rendered globally via Layout:
  1. Floating "Latest Drop" pill — shows for 7 days after each issue,
     dismissible per-issue via localStorage, hides on /newsletter page
  2. Monday admin reminder — only visible to 2 authorized pubkeys
     (founder + second admin), shows every Monday, one-click opens
     Shakespeare AI with prompt pre-filled to write the next issue,
     dismisses for the rest of that Monday via localStorage

- Footer — Newsletter link added to desktop footer alongside License/Supporters

- AppRouter — /newsletter route added (lazy loaded)

To add Issue #002: prepend a new object to NEWSLETTER_ISSUES array
in NewsletterPage.tsx. The pill and Monday reminder auto-update.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 14:39:54 +07:00
shakespeare.diy
f19851da92 security: implement all 8 protection measures
## 1. sanitizeUrl() (already existed, verified applied)
- Applied to ShopDetailPage, CoachDetailPage with urlDomain() tooltip hints
- Blocks javascript:, data:, vbscript: and all non-http(s) schemes
- Returns null for invalid URLs — link simply doesn't render

## 2. Geohash validation in parsers
- Added isValidGeohash() in types.ts using ngeohash.decode()
- Min 4 chars (country-level precision rejected), max 12 chars
- Rejects lat/lng outside Earth bounds
- Rejects 0,0 "null island" — classic bot artifact
- VPN/Tor unaffected: geohash comes from map pin, not user IP

## 3. Content length limits in all parsers
- MAX_TITLE_LEN=100, MAX_DESCRIPTION_LEN=3000, MAX_LOCATION_LEN=200
- MIN_TITLE_LEN=2, MIN_GEOHASH_LEN=4
- Applied to parseSkateSpot, parseSkateShop, parseSkateCoach,
  parseSkateFilmer, parseSkateEditor, parseSkateEvent

## 4. Smart WoT default (already in codebase, verified correct)
- Logged out / <5 follows → 'all' (don't punish new/anon users)
- Logged in with ≥5 follows → 'trusted' (sort trusted first)
- Soft mode: trusted content floats to top, nothing hidden
- Active in MapPage and EventsPage

## 5. Trusted count stats in useGrowthStats
- Added trustedSpots and trustedMappers to GrowthStats type
- Heuristic: pubkeys with ≥2 published items = organic community member
- CommunityStatsPage now shows "🛡 X from active accounts" below spot count
- Purely additive — both numbers always visible

## 6. OfficialBadge component (already existed, wired up)
- Added to SpotCard, EventCard, ShopDetailPage, CoachDetailPage
- Only renders for FOUNDER_PUBKEY (20e7c953...)
- Zero-friction: invisible for all other pubkeys
- Impersonation attempts immediately visible

## 7. Leaflet marker clustering
- Installed leaflet.markercluster v1.5.3 + @types
- MarkerClusterGroup replaces direct marker.addTo(map)
- Custom cluster icon: red/orange gradient circle with count
- maxClusterRadius: 60px, spiderfyOnMaxZoom: true
- MarkerCluster CSS loaded from CDN in index.html

## 8. DM phishing warning banner
- PhishingWarningBanner component in DirectMessagesPage
- One-time dismissible (localStorage key: dm-phishing-warning-dismissed)
- Message: SkateSpots will never DM you asking for keys/payments
- Uses useLocalStorage hook, Shield icon, X dismiss button

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 14:03:15 +07:00
shakespeare.diy
0451887a17 feat: extended community stats page with Nostr growth data
New useGrowthStats hook:
- Queries wss://goskatespots.nostr1.com directly (authoritative source)
- Fetches all 7 content types in parallel: spots, shops, events,
  coaches, filmers, editors, tours
- Deduplicates addressable events (keep newest per pubkey+kind+d)
- Buckets events by month with gap-filling for continuous X axis
- Computes cumulative spots line chart data
- Extracts top 10 mappers by spot count
- 15 min stale time — stats don't need to be live

New useGeyserStats hook:
- Queries Geyser GraphQL for supporter count + total sats raised
- Reuses same API already used in SupportersPage

Rewritten CommunityStatsPage:
- Section 1: 8 hero stat cards (spots, shops, events, coaches,
  filmers, editors, tours, mappers) with per-type accent colours
- Section 2: 4 charts (cumulative spots area, monthly stacked bar,
  active mappers bar, content mix donut)
- Section 3: Top 10 mappers leaderboard with avatars + 🥇🥈🥉
- Section 4: Supporters banner with live Geyser count + sats raised
- Footer: data source + verifiability note
- Full skeleton loading for each section independently
- Error state for relay connectivity issues

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 12:54:32 +07:00
shakespeare.diy
0e5abcb2db fix: guard against undefined metadata in AccountSwitcher
user.metadata can be undefined when the profile hasn't loaded yet.
Changed all .metadata.x accesses to .metadata?.x to prevent crash.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 11:39:32 +07:00
shakespeare.diy
bd3e4bee4e perf: lazy loading, faster timeouts, better caching, chunk splitting
- AppRouter: lazy load all 40+ pages (only Index is eager)
  → initial JS bundle no longer includes code for pages never visited

- MapPage: lazy load SpotMap/Leaflet (~150KB) behind Suspense
  → Leaflet only downloads when user navigates to /map

- useSkateSpots/useShops/useMarketplace/useTours:
  → timeout: 30s → 8s (fail fast, show error sooner)
  → staleTime: 30s → 5min (trust cache, fewer relay round-trips)
  → gcTime: 5min → 30min (keep data in memory longer)
  → refetchOnMount: 'always' → true (use cache when data is fresh)

- Remove console.log/warn/debug spam from production hooks

- vite.config: manual chunk splitting for leaflet, nostr-tools, bitcoin
  libs, radix-ui, react-router — each chunk cached independently
  by browser, only re-downloaded when that library updates

- vite.config: esbuild drops console.log/warn/debug in prod build

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 11:32:24 +07:00
shakespeare.diy
d844e151bd Fix CVM relay URL: relay.contextvm.net → relay.contextvm.org
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 11:13:51 +07:00
shakespeare.diy
0f34105e66 Update logo: stylish skateboard deck with location pin on top
New icon design inspired by the skateboarding arch logo style:
- Skateboard deck (popsicle shape) in the bottom portion with gradient colors
- 4 wheels with gradient accents and white hub centers
- Metal truck hangers visible below the deck
- Location pin centered on top with glow effect
- Brand gradient: #ff0040 (red) → #ff6600 (orange) → #ccff00 (lime)
- No text, no fire - clean and stylish on black background

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 01:01:00 +07:00
shakespeare.diy
32eda7996e Rework logo: minimal skateboard deck at pin tip, original design preserved
- Gradient circle and white map pin exactly as original
- Small horizontal white skateboard deck (popsicle shape) sits right at the 
  tip of the pin point, minimal and clean
- Four small wheel circles at deck corners
- Everything inside the gradient circle, nothing breaking outside

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 00:50:52 +07:00
shakespeare.diy
05a74ed4f8 Fix logo: original pin+circle design preserved, white skateboard deck clearly visible below
- Keep original gradient circle (red→orange→lime) and white map pin exactly as-is
- White skateboard deck drawn behind the circle, protruding below it
- Deck has popsicle shape (rounded nose + tail), two truck axles, four wheels
  that extend out beyond the deck sides so they're clearly visible
- Wheels have inner hub detail for realism

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 00:48:44 +07:00
shakespeare.diy
3cba958437 New logo: map pin on top of skateboard deck, same gradient colors
- Redesigned icon.svg: skateboard deck shape (rounded nose/tail, truck axles,
  wheels) beneath the existing map pin, all using the same red→orange→lime
  gradient as the original
- Replace MapPin component icons in desktop header, mobile header, and footer
  with the actual icon.svg image so the new logo shows everywhere

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 00:46:05 +07:00
shakespeare.diy
fad3a3323d Replace Impact with Bebas Neue + Barlow Condensed for authentic skate typography
- Install @fontsource/bebas-neue and @fontsource/barlow-condensed
- Add .font-display (Bebas Neue) and .font-condensed (Barlow Condensed) CSS utilities
- Hero headline: split into two lines SKATE / SPOTS with clamp() fluid sizing,
  Bebas Neue replaces Impact throughout
- Subtitle: Barlow Condensed 700 with wide letter-spacing
- Description: Barlow Condensed italic
- All section headings (Featured Spots, Events, The Lab, CTA, New Here?): Bebas Neue
- Section subheadings: Barlow Condensed with tracked uppercase
- Desktop + mobile header logo: Bebas Neue
- Footer logo: Bebas Neue
- Tagline badge: Barlow Condensed 600

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 00:37:32 +07:00
shakespeare.diy
0f98994c86 Fix NostrSync error toast for accounts with no profile published yet
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-17 23:38:24 +07:00
shakespeare.diy
6e46214747 Fix profile not loading after Amber login - rewrite NostrSync
Root causes found:
1. syncedUsers blocked re-sync permanently even on failure — first attempt
   fires before WebSocket connections are ready on mobile, fails silently,
   gets marked done, profile stays anonymous forever that session
2. useAuthor had 5min staleTime — empty {} result from pre-login query
   was cached and not refetched after login
3. NostrSync did not force-remove the stale useAuthor cache entry

Fixes:
- Rewrite NostrSync with proper retry logic (up to 5 attempts, backoff)
  - Tracks success/failure per pubkey, only stops on confirmed success
  - Re-triggers on user.pubkey change (catches Amber polling completing)
  - Forces queryClient.removeQueries on author cache before each attempt
  - Tries Citrine → bootstrap relays → main pool in order
- useAuthor: reduce staleTime 5min→1min, expand fallback relay list
- Both files now use same BOOTSTRAP_RELAYS set for consistency

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-17 23:35:18 +07:00
shakespeare.diy
4caad49dfb Fix profile/relay loading after Amber login + add Citrine support
Bug 1 - AppProvider was wiping NIP-65 relay list:
- Relay migration check was comparing stored relays vs defaults and
  resetting them if they differed — overwriting the user's own NIP-65 list
- Fix: only reset relays to defaults if updatedAt === 0 (never synced)
  and relays array is empty

Bug 2 - NostrSync not finding profile on mobile:
- Add Citrine local relay detection (ws://localhost:4869)
  - If Citrine is running, query it first — instant local data
- Add BOOTSTRAP_RELAYS (purplepag.es, relay.nostr.band, nos.lol)
  - Always try these for profile+relays before main pool
- syncRelays() now accepts optional relay group param
- Increase initial delay to 1500ms in PWA mode (800ms in browser)
  to give WebSocket connections time to establish after Amber redirect

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-17 23:15:54 +07:00
shakespeare.diy
65914303f1 Fix Amber login for PWA installed to home screen
PWA standalone mode is single-tab. Opening nostrsigner: with a callbackUrl
sends the callback to the browser (a different app), not back to the PWA.
The PWA tab is suspended and never sees the result.

Fix:
- Detect PWA standalone mode via matchMedia('(display-mode: standalone)')
- In PWA mode: open Amber WITHOUT a callbackUrl — Amber copies pubkey to
  clipboard automatically when no callback is given
- Use visibilitychange to detect when user returns to PWA after approving
- Auto-read clipboard on return and complete login silently
- Manual "I approved" button now tries localStorage first (browser), then
  falls back to clipboard read (PWA) — covers both contexts with one button
- Browser mode unchanged: localStorage cross-tab polling still works

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-17 23:04:48 +07:00
shakespeare.diy
0c87fa5827 Fix Amber login not completing on GrapheneOS new-tab callback
Root cause: on GrapheneOS, Amber opens /amber-callback in a BRAND NEW TAB.
login.amber() was being called in that new tab's React instance, which has
no effect on the original tab where the user is waiting.

Fix:
- AmberCallbackPage now writes pubkey to localStorage('amber_pending_pubkey')
  instead of calling login.amber() — a cross-tab signal
- LoginDialog polls for 'amber_pending_pubkey' every 400ms and calls
  login.amber() itself in the correct React context
- Manual "I approved" button also reads the same key
- Removed reliance on getAmberLogin() which checked the wrong localStorage key

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-17 22:46:39 +07:00
shakespeare.diy
67850348e6 Fix scroll broken on mobile - remove overscroll-behavior:none and webkit-overflow-scrolling:touch from globals
Both were added in the last mobile optimization and caused touch scroll
to stop working on Android/GrapheneOS browsers:
- overscroll-behavior: none on html/body blocks native touch scroll
- * { -webkit-overflow-scrolling: touch } on every element interferes
  with the browser's native scroll handling

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-17 22:39:28 +07:00
shakespeare.diy
4398c2e73b Fix Amber login stuck on GrapheneOS/Android browsers
Root cause: window.location.href navigates the tab away entirely on Android,
destroying sessionStorage. When Amber calls back to /amber-callback the login
completes there, but returning to the dialog the state is lost.

Fixes:
- Switch from sessionStorage to localStorage for amber_return_path and
  amber_login_attempt_ts — survives tab navigation on Android
- Add 500ms polling loop in handleAmberLogin that checks getAmberLogin()
  every 500ms — detects when AmberCallbackPage stored the pubkey and
  auto-completes login without needing visibilitychange to fire
- Add "I approved — complete login" manual button in waiting state as
  explicit fallback for stuck users (reads localStorage right away)
- AmberCallbackPage now uses localStorage for all persistence
- Clean up poll interval on dialog close and unmount

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-17 22:37:26 +07:00
shakespeare.diy
03d0fe93b0 Improve Amber login flow - zero friction, auto-complete on return
- Add dedicated /amber-callback route so Amber always has a stable return URL
  - Handles get_public_key (login), sign_event, nip44/nip04 encrypt/decrypt
  - Shows animated processing screen while completing, then redirects back
- Add subscribeToAmberReturn() helper in amber.ts using visibilitychange
  - Auto-detects when user returns to the tab after cancelling in Amber
  - Resets login UI to initial state gracefully
- Update buildCallbackUrl() to use /amber-callback instead of current page URL
- Rewrite Amber tab in LoginDialog:
  - Remove 2-step clipboard paste flow entirely
  - Single tap opens Amber, login completes automatically on return
  - Waiting state shows animated dots + cancel button
  - No manual pubkey entry needed
- Save return path in sessionStorage so AmberCallbackPage redirects user back

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-17 22:29:06 +07:00
shakespeare.diy
aac2b7b9a2 Improve mobile/Android UI responsiveness
- Add xs (375px) breakpoint to Tailwind config for small phone targeting
- Fix main content bottom padding to account for safe-area-inset-bottom
- Redesign mobile bottom nav: active dot indicator, proper safe-area padding,
  flex-1 tabs that scale to any width, tighter text on xs screens
- Fix hero section: smaller padding/text on tiny phones (320-375px),
  horizontal badge wrapping, full-width buttons stack below xs
- Add CSS improvements: overscroll-behavior, -webkit-text-size-adjust,
  input font-size fix for iOS zoom prevention, scroll-x helper class,
  touch-friendly min tap target sizes

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-17 22:04:29 +07:00
shakespeare.diy
1441855eb1 feat: add Capacitor + Zapstore config for Android APK distribution
- capacitor.config.ts: Capacitor config with dark theme, WebSocket support,
  splash screen, status bar, keyboard plugins
- zapstore.yaml: Zapstore publish config with npub identity verification
- ZAPSTORE.md: full step-by-step guide (clone → build APK → publish to Zapstore)
- package.json: android:init, android:sync, android:open, android:build scripts
- App ID: org.skatespots.app

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-17 16:11:28 +07:00
shakespeare.diy
a3cd6cf9f4 feat: Sasquatch clickable links, weather tool, ContextVM footer
- ChatText component renders markdown [label](url) and bare URLs as neon clickable links
- 👉 URL patterns also rendered as clickable links
- get_weather tool: real-time weather + skateability score via Open-Meteo
  Ask "weather in Bangkok?" or "is it good to skate in Barcelona today?"
- Footer updated: "Powered by ContextVM & Nostr Relays Data" with ContextVM
  linking to https://contextvm.org/s/e3564bb877...
- Weather geocodes city name via Nominatim, returns temp/conditions/wind/skateability

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-17 15:55:53 +07:00
shakespeare.diy
f9c0f3c5ac feat: redesign Sasquatch UI - dark jungle aesthetic, mobile-first
- Dark forest green + neon lime (#ccff00) theme matching app's THUG2 aesthetic
- Mobile: compact strip above bottom nav, no more full-width overlay
- Minimized: small round button with neon pulse, lives at bottom-left
- Jungle texture strips top/bottom, dark gradient background
- Chat bubbles: neon red for user, neon lime for Sasquatch
- Input styled to match dark app theme
- Neon glow effects, live dot indicator
- Works cleanly on all screen sizes

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-17 12:50:41 +07:00