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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>