Commit graph

122 commits

Author SHA1 Message Date
shakespeare.diy
f010a61608 fix: use nostr-signer-capacitor-plugin for Amber NIP-55 login
All previous URL-scheme approaches (nostrsigner:, intent:) were fundamentally
wrong for a Capacitor app. JavaScript in a WebView cannot reliably dispatch
Android intents via URL navigation.

The correct solution — used by Coracle and other Capacitor-based Nostr apps —
is the nostr-signer-capacitor-plugin which communicates with Amber via native
Android intents through the Capacitor bridge:

  NostrSignerPlugin.setPackageName({ packageName: 'com.greenart7c3.nostrsigner' })
  NostrSignerPlugin.isExternalSignerInstalled()
  NostrSignerPlugin.getPublicKey() → { npub }

This is synchronous from the JS side, handles the entire Android intent
roundtrip natively, and returns the pubkey directly. No URL encoding,
no callback URLs, no redirects, no malformed request errors.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-08 11:59:04 +07:00
shakespeare.diy
92f317f5e5 fix: Amber login for GrapheneOS — clipboard + paste flow
GrapheneOS Vanadium browser blocks intent: URLs from web pages as a
security hardening measure. This is why every URL format we tried
caused "malformed request" — Vanadium was blocking the intent before
Amber ever saw it.

New flow that works on ALL Android browsers including GrapheneOS:
1. Tap "Open Amber" → navigates to nostrsigner: URL with no callbackUrl
   (Amber copies pubkey to clipboard when no callbackUrl is given)
2. User approves in Amber, comes back to app
3. App tries navigator.clipboard.readText() automatically on visibilitychange
4. If clipboard permission denied → shows paste input for hex pubkey
5. User pastes pubkey → logged in

No intent: scheme, no callback URL, no redirect loop. Simple and bulletproof.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-08 11:43:59 +07:00
shakespeare.diy
712b871e1a fix: Amber intent: format + # in callbackUrl breaks intent parsing
Three compounding bugs fixed:

1. WRONG FORMAT: Was using nostrsigner: directly, then intent: with encoded
   values. Amber README shows intent: with raw unencoded S.key=value params.

2. # IN CALLBACKURL: Our callbackUrl was https://.../#/amber-callback which
   contains a # — but intent: uses # as its own delimiter (#Intent;...;end).
   Having a # inside the callbackUrl value breaks the entire intent string.
   Fixed: callbackUrl is now https://.../amber-callback?event= (no hash).

3. HASHROUTER MISMATCH: Amber opens /amber-callback (no hash) but HashRouter
   only handles /#/route paths. Fixed in main.tsx: pre-React IIFE detects
   /amber-callback path, saves ?event= to sessionStorage, then redirects to
   /#/amber-callback so React Router matches AmberCallbackPage correctly.

Final correct intent URL format:
  intent:#Intent;scheme=nostrsigner;S.compressionType=none;S.returnType=signature;S.type=get_public_key;S.callbackUrl=https://skate-spots.org/amber-callback?event=;end

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-08 11:36:03 +07:00
shakespeare.diy
6f303d1956 fix: revert to nostrsigner: format, fix double-encoding of callbackUrl
Root cause of "malformed request":
- buildIntentUrl() was calling encodeURIComponent() on ALL values including
  callbackUrl, which was ALREADY a full URL with special chars
- This double-encoded the callbackUrl, arriving at Amber as garbage
- The intent: wrapper format was also unnecessary

Fix: back to clean nostrsigner: format per NIP-55 spec
- Only callbackUrl gets encodeURIComponent (once)
- All other params are plain query string values
- Matches exactly what NIP-55 spec shows in its example code

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-08 11:26:42 +07:00
shakespeare.diy
c30546f700 fix: use Android intent: URL format for Amber (not nostrsigner: directly)
The nostrsigner: scheme used directly caused "malformed request" in Amber
when opened from a browser context. The correct format per Amber's README
for web clients is the Android intent: URL format:

  intent:<payload>#Intent;scheme=nostrsigner;S.type=...;S.callbackUrl=...;end

Updated all Amber operations (login, sign, nip04/nip44 encrypt/decrypt)
to use buildIntentUrl() helper instead of raw nostrsigner: URLs.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-07 19:35:00 +07:00
shakespeare.diy
db157efda4 fix: use window.location.href for Amber NIP-55 intent (not window.open)
window.open(..., '_blank') opens a new browser tab instead of firing
the Android intent — causing Amber's "malformed request" error.

Per NIP-55 spec the correct method is window.location.href = url,
which triggers the nostrsigner: intent that Amber is registered to handle.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 14:17:51 +07:00
shakespeare.diy
59eda93641 fix: Amber login callback URL for HashRouter
BUG #1 (critical): buildCallbackUrl() was returning
  https://skate-spots.org/amber-callback?event=
but the app uses HashRouter, so React Router never matched /amber-callback.
Amber would open the callback URL in a new tab, land on the homepage, and
AmberCallbackPage would never render — login was silently broken.

Fixed to:
  https://skate-spots.org/#/amber-callback?event=

BUG #2: AmberCallbackHandler (global, in App.tsx) was competing with
AmberCallbackPage on the /amber-callback route, causing a race condition
where the global handler could consume the pubkey before the page component.
Fixed by detecting the hash path and early-returning when on /amber-callback.

BONUS: openAmberForLogin() now persists amber_return_path from the current
hash path so AmberCallbackPage can navigate back to where the user was.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 13:58:41 +07:00
shakespeare.diy
4f7852fec7 Fix Amber login: replace nostrconnect/relay flow with proper NIP-55 intent flow
The entire nostrconnect relay approach was wrong for a native Android/Capacitor app.
Amber on Android uses NIP-55 intents (nostrsigner: URI scheme), not NIP-46 relays.

New flow (same as Amethyst):
1. openAmberForLogin() fires nostrsigner:?type=get_public_key&callbackUrl=...
2. Amber opens, user taps approve
3. Amber calls back to org.skatespots.app://amber-callback?event=<pubkey>
4. AmberCallbackPage writes pubkey to localStorage('amber_pending_pubkey')
5. LoginDialog polls localStorage every 500ms and calls login.amber(pubkey)
6. Done — no relays, no waiting, no spinning

Also added login.amber(pubkey) method to useLoginActions.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 13:24:46 +07:00
shakespeare.diy
e3dd66d078 Fix Amber login: store as 'amber' type (NIP-55) not 'bunker' (NIP-46)
The nostrconnect flow was incorrectly creating a bunker login and trying
to do NIP-46 key exchange. Amber signs as the user's own key directly,
so the pubkey from the connect response IS the user pubkey. Now stores
a proper amber login which uses AmberSigner via useCurrentUser.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 12:38:56 +07:00
shakespeare.diy
03ea569a2a Amber login: poll relay every 3s after returning from Amber, add elapsed timer + Check Now button
- Replace one-shot visibility handler with pollRelay() called immediately on foreground + every 3s
- Add elapsed time counter (M:SS) so user can see the app is still trying
- Add 'Check Now' button for manual relay poll without generating a new URI
- Poll interval auto-clears on success, timeout, or cancel

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 12:34:20 +07:00
shakespeare.diy
dfb60674c5 Fix Amber timeout: increase to 3 minutes so user has time to approve in Amber and return
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 12:31:48 +07:00
shakespeare.diy
bd149fdb2c Fix Amber nostrconnect flow: 20s timeout, error state, stale closure fix, better relays, debug logs
- Replace wss://relay.primal.net with wss://nos.lol (more reliable for NIP-46)
- Shorten timeout from 10 minutes to 20 seconds
- Fix stale closure bug: track amberStep in amberStepRef so timeout can correctly reset UI
- Add 'error' step with red UI and 'Try Again' button instead of endless spinner
- Extend since filter from 5s to 10s in the past
- Add console logs for URI, relays, incoming events, decryption result, secret matching

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 11:48:03 +07:00
shakespeare.diy
24d71679af Layout: remove Sasquatch AI (/pyramid) from mobile nav and clean up Bot icon import
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-28 13:05:18 +07:00
shakespeare.diy
2c3697ba93 SupportersPage: use red transparent No Teeth logo
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-27 22:08:25 +07:00
shakespeare.diy
e03150c19f SupportersPage: remove Geyser from Friends, update No Teeth entry with white logo, Instagram link, and correct tagline
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-27 22:06:27 +07:00
shakespeare.diy
10b9d646fc SupportersPage: add No Teeth Skateboarding to Friends section with red logo
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-27 22:04:11 +07:00
shakespeare.diy
4aaa7007bf AboutPage: remove 'Learn About Nostr' card and nostr.com link
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-27 21:30:26 +07:00
shakespeare.diy
4ffa39ee95 Various UI cleanups: remove Nostr footer link, hide Map&Earn, update NostrHub links
- Index.tsx: remove Nostr.com button from homepage footer (next to FAQ/About)
- SupportersPage: change 'View on GitHub' → 'View on NostrHub' with correct naddr URL
- Layout: remove 'Map & Earn' (/bounties) from desktop More dropdown menu
- Layout: remove 'Map & Earn' (/bounties) from mobile slide-out nav
- Layout: clean up unused Gift icon import and stale isActive check

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-27 21:27:10 +07:00
shakespeare.diy
8633b38125 fix: friends CTA - only if you have the vibes
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-27 19:15:00 +07:00
shakespeare.diy
5c49946e5b fix: Geyser powered by emoji ⚡ -> 🧦
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-27 19:11:02 +07:00
shakespeare.diy
affe663291 fix: friends subtitle -> People we vibe with
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-27 18:26:52 +07:00
shakespeare.diy
80be48275e fix: update No Teeth tagline to Handmade Long Lasting Skateboards
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-27 18:02:26 +07:00
shakespeare.diy
089c6c234c feat: add No Teeth Skateboarding as first Friend, clear Partners
- Partners: removed all entries, now shows 4 placeholder slots
- Friends: added No Teeth Skateboarding with red transparent logo
  linking to instagram.com/noteethskateboarding
- Friends grid: always shows real entries + fills remaining slots to 5
  with "Coming soon" placeholders

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-27 17:57:41 +07:00
shakespeare.diy
baf4c5e10d feat: redesign /supporters with 4 sections + footer label update
- Supporters: existing Geyser leaderboard (unchanged)
- Collaborators: new section, empty placeholder with GitHub CTA
- Partners: Geyser, Angor, Zapstore, ContextVM, MKStack
- Friends: placeholder grid (5 slots) + logo spec instructions
- Footer: "Supporters" → "Collaborators & Friends"
- Static COLLABORATORS/PARTNERS/FRIENDS arrays at top of file for easy editing

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-27 17:51:11 +07:00
shakespeare.diy
5dffa5cac3 feat: UX improvements - credits, draggable sasquatch, mobile fixes
1. AboutPage: add Credits section (Zapstore, ContextVM, Angor, Geyser, MKStack,
   Shakespeare, Nostr), add Zapstore download button, fix powered-by text overflow
   on mobile (wrap-friendly flex layout)

2. SasquatchGuide: make minimized bubble draggable on all devices
   - pointer events for mouse, touch, and stylus
   - saves position to localStorage, persists across sessions
   - tap still opens, drag moves (5px threshold to distinguish)

3. Layout (burger menu): add overflow-y-auto + pb-8 to SheetContent so
   mobile menu is scrollable when content exceeds screen height

4. CashuWalletCard (wallet): fix input+button rows overflowing on mobile
   - flex-col on small screens, flex-row on xs+ (stacked on phones)
   - whitespace-nowrap on buttons to prevent text wrapping

5. LabPage: compact action buttons on mobile (h-8, text-[10px], px-2)
   so all 4 buttons fit in 2x2 grid without overflow/truncation

6. LoginDialog (Amber): improve visibility-change handler - use fresh
   SimplePool subscription on each foreground return instead of querySync
   (which doesn't exist in nostr-tools v2.13)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-23 09:33:46 +07:00
shakespeare.diy
84d80ac3a7 fix: handle Capacitor WebSocket suspension in NIP-46 Amber login
- Add visibilitychange listener: when app returns to foreground after Amber,
  does a one-shot relay query to catch Amber's connect response
- Store pending nostrconnect session in localStorage so it survives app suspend
- Keep live relay subscription as fallback for web/PWA
- Add nsec login tab as reliable fallback option
- Add proper step states: idle -> connecting -> waiting -> success

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-19 00:38:38 +07:00
shakespeare.diy
789a3d7a60 fix: use nostr-tools SimplePool+nip04 for NIP-46 relay subscription
Replace NRelay1 async iterator (unreliable) with nostr-tools SimplePool.subscribeMany()
using onevent callback — same pattern as Ditto/Coracle/Nostrudel.

- Switch to relay.nsec.app, relay.damus.io, relay.primal.net (support ephemeral events)
- Use nip04.decrypt() from nostr-tools for Amber's connect response decryption
- Proper cleanup of pool and subscription on abort/close
- Fall back to signerPubkey as userPubkey if NConnectSigner.getPublicKey() fails

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-19 00:28:46 +07:00
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
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
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
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