Add visually-hidden (sr-only) titles to the three rendered dialogs/sheets
that were missing them:
- OnboardingPage relay-setup dialog (RelayEducation)
- Layout mobile navigation sheet
- MapPage spot detail bottom sheet
These satisfy Radix's accessibility requirement (screen readers announce
the dialog purpose) without altering the visual design.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- CrewChat: founder+admins can hide chat messages via a per-message hide button; reuses useCrewModeration/useHideCrewPost with the rumor (kind-14) message id
- Hidden messages filtered from chat for everyone; moderators see them greyed + "Hidden" (transparent/reversible)
- Since NIP-17 messages are E2EE and can't be deleted from relays, moderation is enforced client-side via public NIP-32 labels (label reveals only the hidden id, never content)
- Wire admins/crewCoord/canModerate from CrewDetailPage; header hint for moderators
- NIP.md: expand moderation section to cover both feed (kind-1 note id) and chat (kind-14 rumor id) with the E2EE client-side caveat
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- useCrewModeration: fetch NIP-32 `hidden` labels authored by founder+admins for a crew; useHideCrewPost publishes a kind-1985 hidden label (L/l org.skatespots.moderation, e=post, a=crew)
- CrewDetailPage feed: moderators can hide posts via an EyeOff button + confirm dialog; hidden posts filtered from the feed for everyone, shown greyed/"Hidden from feed" to moderators (reversible/auditable)
- CrewInviteDialog: founder+admins share an invite link/message (native share + copy); founder gets a shortcut to add a member key in crew settings
- Gate all admin actions on useCrewRole().canManage; moderators = founder + delegated admins
- Add MODERATION_LABEL_NAMESPACE/HIDDEN constants
- NIP.md: document feed moderation label format (author must be founder/admin) + invite semantics
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- SkateCrew: add nip05 + admins fields; parse `admin` role marker on p tags (admins = subset of members)
- CrewForm: NIP-05 input + per-member "Make admin" toggle; write nip05 tag + admin-marked p tags (works on create & edit via shared form)
- useCrewNip05: verify crew handle on ANY domain against the founder pubkey (verified/mismatch/unreachable), CORS-proxy fallback + caching
- CrewNip05Badge: green ✓ / red ⚠ / muted handle states with tooltips
- CollectiveMembers: show Admin chips, sort admins first, admin count in summary
- useCrewRole: centralized founder/admin/member/none roles (canManage, canEditProfile) — founder owns identity, admins get delegated powers with their own keys; egalitarian shared-editing intentionally unsupported
- CrewDetailPage: NIP-05 badge by crew name, viewer role badges (admin/member)
- NIP.md: document crew nip05 verification, admin role marker, and governance/conflict-resolution model
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Lab:
- useLabProfiles sorts featured (Pro) profiles first
- ProBadge shown on Lab cards (LabPage) + Lab detail page; featured cards
get a subtle amber highlight
Events (same no-paywall model as Lab):
- New FeaturedEventUpgrade: every event posts for free; optional fee adds a
featured badge + top placement (reuses existing featuredEvent fee)
- CreateEventPage: removed the paid-event fee gate; opt-in featured toggle,
writes a `featured` tag when active
- useSkateEvents sorts featured events first; EventCard shows ProBadge
(compact in list view, overlay on the image in full view)
Docs:
- NIP.md documents the `featured` tag (unix-seconds expiry, self-attested)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Basic Lab profiles are now always free to publish. The 5,000 sat fee is
opt-in and unlocks a Pro badge + featured placement instead of gating
publication behind a rate.
- types.ts: `featuredUntil` field on all 5 Lab types + SkateEvent,
parseFeaturedUntil() + isFeaturedActive() helpers, wired into parsers
- usePlatformFee: exposes featuredUntilSeconds for the `featured` tag
(platform owners get a 1yr window)
- New ProBadge + LabProUpgrade (opt-in toggle → reveals PlatformFeePayment)
- All 5 Become pages: removed rate-based fee gate and wallet-required gate;
publish is free; when Pro is active a `["featured", "<expiry>"]` tag is added
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Built on the existing NIP-17 gift-wrapped group DM system (useGroupConversation
+ useSendDirectMessage), so the relay only ever sees encrypted blobs.
- New CrewChat component: message list w/ author profiles, composer
(Enter to send), auto-scroll, skeleton/empty states
- Participants = founder + all listed members (deduped)
- Only crew members can read/write (others see a locked "ask to join" state);
warns if the signer lacks NIP-44
- "Private" lock badge makes the E2E-encrypted nature unmistakable
- Surfaced as a card on the crew detail page under the Crew Feed
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- useGrowthStats now queries kind 35004 (photographers) and 35005 (builders),
which were previously omitted; added to totals, monthly buckets, allContent,
and the content-mix donut (folded into the "Lab" slice)
- Community Stats page reorganized into clear sub-sections:
* "The Lab · Creators & Services" — Coaches, Filmers, Editors,
Photographers, Builders, with a total badge
* "Community" — Tours, Crews
This scales the growing role list cleanly instead of cramming one row.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Spot Chat:
- Compact TrustFilter dropdown in the chat header (All / Verified / Trusted / Following)
- Filters messages by author trust via useBatchTrustScores; own messages
always stay visible; shows a "N hidden by your trust filter" indicator
Community stats:
- useGrowthStats now queries kind 35006 crews (was missing entirely)
- totalCrews added to totals + monthly buckets + content-mix donut
- New "Crews" StatCard on the Community Stats page
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Inspired by BitChat's location-channel concept, reframed for skaters and
kept fully SkateSpots-native (own kind + tag conventions, no interop).
- New ephemeral kind 24899 tagged with a geohash `g` (documented in NIP.md)
- useSpotChat / useSendSpotChat hooks (polled, community-relay aware)
- SpotChat component: live message list, composer, author profiles,
Block/Neighborhood/City precision toggle, and light IRC-style flavor
(/me action messages, /who participant count)
- Spot detail pages now show a per-spot block-level channel
- Map gains a "Local Chat" sheet anchored to the map center with the
precision selector
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- useCrewZap hook: splits a total evenly across recipients, resolves each
member's Lightning address (batched kind-0 query), fetches an invoice per
recipient and auto-pays via NWC → WebLN; per-recipient status
(resolving/paying/success/skipped/failed) with manual-invoice fallback
- splitAmount util distributes remainder fairly (no lost sats)
- ZapCrewDialog: amount presets + custom, per-head preview, live progress rows,
copy-invoice fallback when no wallet; excludes the current user
- CrewDetailPage: "Zap the Crew" button (recipients = founder + confirmed
members via useCollectiveMembers); shown only when >1 recipient
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Nutzap:
- Gate sending behind NUTZAP_SENDING_ENABLED=false with clear docs; sending
would require P2PK-locking proofs (fund-loss risk if wrong), so it's disabled
until implementable/auditable. Receiving + reading unchanged. No dead-end
button exists (hook was never wired to UI).
Cross-linking crews ↔ Lab collectives:
- types: parseAddrCoord/parseCrewRef/parseLabRefs helpers + LAB_KINDS; add
crewRef to all 5 Lab interfaces/parsers and labRefs[] to SkateCrew
- CrewLinkField (pick one of your own crews) added to all 5 Become-* forms,
shown only for collectives; publishes an a-tag on the Lab profile
- LabLinkField (pick your own Lab profiles) added to CrewForm; publishes a-tags
- CrossLinkCards: LinkedCrewCard on Lab detail ("Part of a Crew"), LinkedLabCard
on crew detail ("Hire This Crew"), resolving coordinates via useCrew/useLabProfile
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Remove LGBTQ+ and Longboard preset vibes
- Rename "Girls / Womxn" → "Girl / Woman"
- Add custom vibe/focus field: users define their own #hashtag focus
(normalized to a lowercase hyphenated slug, published as t tags)
- Support multiple "Other Socials" links (SkateCrew.socials[]) with add/remove UI;
parser collects all social tags; detail page renders each (URL or plain handle)
- Update NIP.md crew vibe taxonomy
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Phase 1 CLINK integration (recognize & display only):
- types.ts: optional noffer field + isValidNoffer guard on SkateCoach,
SkateFilmer, SkateEditor, SkatePhotographer, SkateShop; parsers read it
- NofferField: reusable opt-in input with live validation, added to all 4
Become-* Lab forms and both Create/Edit shop forms
- ClinkPayButton: secondary "Pay via CLINK" action (QR + copy + nostr: deep
link), rendered on CoachDetailPage and ShopDetailPage only when a valid
noffer is present; never replaces the existing zap / Lightning Address flow
- useQRCodeScanner: classify scanned noffer1/ndebit1 CLINK codes
- NIP.md: document the noffer tag on Lab kinds + shops and a CLINK section
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
New Photographers category (blue-indigo) — separate from Filmers:
- New addressable kind 35004 + SkatePhotographer type + parseSkatePhotographer.
- Rich photographer taxonomy as queryable `t` tags: services (action,
sequence, editorial, product, events…), camera bodies, lenses, lighting
(Profoto/Elinchrom/Godox/triggers…), support/rig, and shooting style,
plus a freeform "gear" tag and a portfolio link.
- BecomePhotographerPage form with chip-grid selectors for all gear
categories + "what's in the bag" textarea, wired to /become-photographer.
- The Lab: 4th "Photo" tab (indigo accent) with its own filters, cards,
empty state; OfferFAB gains "Offer Photography"; useLabProfiles fetches
35004; CoachDetailPage renders photographers (and editors) properly with
per-type accent, gear + portfolio display.
- NIP.md: documented the whole Lab (kinds 35001-35004) incl. photographer.
Filmer improvements (now video-focused, photography split out):
- Removed the token "Action Photography" service; added follow/line filming.
- Richer equipment list: cinema camera, HVX, action cam, gimbal, 4K/HFR.
- Filmers gain a freeform "what's in the bag" gear field + a reel/portfolio
link, both surfaced on the detail page.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Events can now be discovered directly on the map:
- New "Events" layer (lime #ccff00, calendar pin marker) plotting upcoming,
geo-located events. Off by default — users opt in via the Layers control.
- Event markers show a popup with title, date/time, location, and a
"View Event" button linking to the event detail page.
- Only future/ongoing events with a valid geohash are plotted (past events
are filtered out to keep the map clean).
- Added to the mobile Layers popover, desktop Layers popover, and the mobile
filter drawer's "Show on Map" grid. Layer count badge now tracks 5 layers.
- SpotMap gains `events` + `showEvents` props and a createEventIcon marker.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
#6 Desktop parity:
- Replaced the four separate Spots/Shops/Gear/Tours toggle buttons in the
desktop map toolbar with a single grouped "Layers" popover (with count
badge), matching the mobile control. Declutters the dense toolbar row.
#7 Entity color discipline:
- Locked the four map-entity colors to a single canonical value each,
matching the map markers (source of truth): Spots #ff0040, Shops #9333ea,
Gear #f97316, Tours #06b6d4.
- Fixed the mismatch I'd introduced (Layers popover + Add FAB used #a855f7
purple instead of the marker's #9333ea). Everything now agrees.
Note: broader purple usage (Stash, Cashu, NWC, Editors) is intentionally
left as-is — those are separate semantic uses, not the map entity language.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- 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>
Replaces the crowded 12-item vertical right rail with a clean, minimal set:
- Right edge now has just two 44px controls: Near Me and a Layers button.
- Layers opens a labelled popover (Spots/Shops/Gear/Tours) with color dots
and checkmarks, plus a count badge on the button so state is obvious.
- All "add" actions consolidated into a single red speed-dial FAB
(bottom-right) that fans out to Add Spot / Add Shop / Offer Tour /
Quick Snap, with a tap-catcher backdrop to dismiss.
- Sasquatch bubble moved to bottom-left on mobile so it no longer competes
with the Add FAB for the bottom-right corner.
- Larger touch targets (44px) for better native/ZapStore ergonomics.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Sasquatch panel no longer auto-opens on the map (/) or The Lab (/lab).
Added an `autoShow` flag to GuideMessage; only opt-in messages (onboarding)
auto-open. The bubble stays minimized until tapped, and context messages
still populate the panel when opened manually.
- Map CARTO/OSM attribution now sits above the mobile bottom nav (safe-area
aware) instead of overlapping the Shops & Spots buttons; made it smaller
and more subtle. Desktop keeps it in the bottom-left corner.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Architecture:
- usePlatformFee hook: central fee config (featuredEvent/labProfile/featuredShop)
5,000 sats each, 30-day validity, localStorage persistence, owner bypass
- PlatformFeePayment component: generic reusable payment gate
auto-pay via NWC/WebLN, QR fallback, active/expired state display
all fees route to skatespots@minibits.cash
Events (CreateEventPage):
- New optional "Entry Fee" field
- Triggers 5,000 sat platform fee when filled
- Stores entry-fee + paid tags on published event
- Free events: zero friction unchanged
The Lab (Become{Coach,Filmer,Editor}Page):
- Triggers 5,000 sat fee when "Rate" field is filled
- Submit disabled with clear CTA until fee paid
- Free/volunteer profiles: zero friction unchanged
Rule: "pay only when you earn"
- No rate set → publish instantly, no fee
- Rate set → 5,000 sats to SkateSpots, valid 30 days
- Platform owners always bypass
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Hero:
- "SKATE YOUR WORLD" headline with neon red/lime treatment
- Beta badge pill with live pulse dot
- Feature pills row (spots, lab, events, gear, stash, radio, zaps)
- Primary CTA right in the hero — no scrolling to get started
- "No email. No password. Own your identity forever." copy
Layout:
- Removed mechanical 4-step progress tracker (step 2 auto-completing was confusing)
- Replaced with natural scrolling sections with clear headings
- Logged-in state shows green confirmation bar with FounderFollow
What you can do:
- 6-card grid: Map, Lab, Events, Buy/Sell, Stash, Radio — all linked
What is Nostr:
- Skater-friendly explanation with 4 benefit pills
- Deep dive button for relay education modal
Zaps & Wallet:
- "Optional" badge so new users don't feel overwhelmed
- Side-by-side: Cashu (built-in) + External wallet (NWC)
FAQ (accordion — 8 questions, no duplicates):
- "Is it free?" — combined the two identical free questions
- "What is Nostr?" — proper explanation
- "What's an nsec?" — key backup warning
- "What signers work?" — desktop/Android/iOS with real links
- "Can I use it on my phone?" — PWA + Zapstore mention
- "What is a zap?" — simple skater framing
- "Will my spots be deleted?" — relay permanence
- "Private property spots?" — community responsibility
- "How to report a bug?" — links to cordn.net beta coordinator
Beta section:
- cordn.net/p/npub1e2ayjl... as primary beta contact (clickable card)
- Founder npub with Copy + DM buttons as secondary
- Removed v0.1.0 hardcoded version string
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Both + buttons are 36px circles matching the strip visual system
- Add Spot = red circle (bottom of layer toggles section)
- Add Shop = green circle (right below Add Spot)
- Removed redundant left-side FAB entirely — everything is now in one column on the right
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Toolbar:
- Slim rounded-pill search input (was tall boxy default)
- Compact icon-only view toggle pills
- Small circle filter button (red when active, badge count)
Right-side control strip:
- Layer toggles (Spots/Shops/Gear/Tours) as 36px circles
active=opaque, inactive=35% opacity — no more giant emoji FABs
- Near Me button integrated into strip (removed from top-right)
- Quick Snap in strip, same style — no more big red circle
- All transitions: translate-x-2 slide-out when overlays open
Left-side Add FAB:
- Single gradient pill (red→orange) replaces two competing circles
- Smaller footprint, less visual noise
Loading indicator:
- Replaced center-screen "Loading..." pill with slim animated
gradient bar at top edge of map (top-0 left-0 right-0 h-0.5)
Attribution:
- Moved from floating top-right to bottom-left corner
- 9px text, 50% opacity — required but invisible
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Sidebar expanded to 300px with real visual weight
- Inventory card: big stat number (e.g. "5 items in stash"), est. sats value, 3-cell secondary stats row (Wishlist / For Sale / To Trade), clickable category breakdown bars
- Marketplace CTA: gradient header with branded icon, per-type badges showing sell/trade counts, full-width gradient CTA button
- Wishlist teaser card: number callout + "View Wishlist" action button
- Quick-add buttons (Add to Stash / Add to Wishlist) moved to top of sidebar on desktop — header no longer needs them
- Grid defaults to 2/3/4 cols (sm/md/xl) — no more awkward single-item stretch
- Empty states bigger and more purposeful with better copy
- Mobile layout unchanged; FABs and mobile CTA banner retained
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- REMOVED: Sync banner (already in header subtitle)
- REMOVED: ProfileStatsBar self-intro card (it's YOUR page, you know who you are)
- REMOVED: Chunky tab switcher — replaced with underline tabs inside the sticky header,
saving ~50px of vertical space before you see any items
- MERGED: All header info (title + item count + encryption + sats value) into one
compact 56px sticky bar — nothing duplicated
- IMPROVED: Search + category filter in a single tight row (h-9 inputs)
- IMPROVED: Category chips only render when >1 category present (no orphan chips)
- IMPROVED: Sidebar is desktop-only (lg:hidden) — on mobile it was buried below items,
now replaced by a contextual bottom banner that only appears when items are for sale
- IMPROVED: FABs slightly smaller (not competing with Sasquatch bubble)
- IMPROVED: Empty state is more focused — no walls of text
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Merged the two separate marketplace cards (Ready to Sell + Stash→Marketplace)
into a single smart card:
- When items are marked for sale/trade: shows orange "Ready to Sell" CTA
with item count and a prominent Browse Marketplace button
- When no items for sale: shows a quieter grey card explaining how to
mark items for sale, with a subdued Browse Marketplace link
One card, one button, no duplication.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Key improvements:
- NEW: List view (default) — compact swipeable rows for mobile, perfect
for scanning inventory quickly. Grid view still available via toggle.
- NEW: Floating FABs on mobile — red + to add to stash, cyan heart to
add to wishlist, positioned above the nav bar with safe-area support
- NEW: Cross-device sync info banner explaining Nostr = synced everywhere
(answers the user's question: yes, items added on desktop show on mobile)
- FIX: "Browse Marketplace" now navigates to /marketplace (GearSwapPage)
instead of /map?view=gear which incorrectly loaded the map
- NEW: Sticky page header with item count badge, encrypt indicator, and
view-mode toggle — always visible when scrolling
- NEW: Compact ProfileStatsBar replaces the heavy CharacterPanel sidebar card
- NEW: Category quick-chips — tap an emoji to filter by category instantly
- IMPROVED: Location filter only shows locations that have items (no empty chips)
- IMPROVED: Sidebar consolidated — removed redundant cards, kept what matters:
"Ready to Sell" card (only shown if items are marked for sale), marketplace
link, wishlist status, and compact tips
- IMPROVED: Empty states mention cross-device sync as a feature
- IMPROVED: List row actions are always visible (no hover-only on mobile)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- FABs (add spot/shop): switch from fixed bottom-[88px] to
calc(3.5rem + env(safe-area-inset-bottom, 0px) + 0.75rem) so they
respect device safe-area and always clear the bottom nav on all
Android/iOS Capacitor builds
- Right-side filter pills: same fix, switched from absolute to fixed
with the same safe-area-aware bottom value
- useSasquatchGuide: /map page now returns undefined message, meaning
Sasquatch never auto-opens when navigating to Shops & Spots — user
must tap the floating bubble to open it manually
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Root cause: map container is already clipped to exclude bottom nav
(h-[calc(100dvh-var(--header-mobile)-var(--bottom-nav))]) so
bottom-[calc(5rem+...)] was pushing buttons ~80px up inside the
clipped box — wrong and covered filter pills
- Both FABs and right-side pills now use bottom-4 (16px from container edge)
- FABs redesigned as clean 48×48 icon-only circles, no text labels:
red circle (+) → Add Spot
green circle (+) → Add Shop
thick Plus strokeWidth=3, high-contrast (black on green, white on red)
- Only shown when user is logged in (no logged-out ghost button)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Remove speed-dial/tap-to-expand pattern — buttons are now always visible
- "Add Spot" — red (#ff0040), white text, same style as before
- "Add Shop" — green (#00cc66), black text, sits above Add Spot
- Both cleared of bottom nav via bottom-[calc(5rem+env(safe-area-inset-bottom))]
- Removed unused fabOpen state
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
FAB (Add Spot / Add Shop):
- Removed old bottom-left "Add Spot" pill that sat on top of the bottom nav
- New speed-dial FAB in bottom-left, positioned at
bottom-[calc(5rem+env(safe-area-inset-bottom,0px))] — always clears
the iOS/Android safe area + bottom nav height
- Tap the red + button to expand upward: "Add Spot" (red/MapPin) and
"Add Shop" (purple/Store) pill buttons slide in with animation
- + button rotates 45° when open (becomes ×), closes on map tap
- Logged-out state shows "Log in to add" prompt instead
- Tapping either option closes the speed-dial before navigating
Right-side filter pills:
- Bumped from bottom-20 to bottom-[calc(5rem+env(safe-area-inset-bottom))]
so they're consistently safe-area aware on devices with home indicator
Near Me — location denied fix:
- PERMISSION_DENIED no longer shows a toast (which was useless — user
can't act on a disappearing toast)
- Tapping the red LocateOff button now opens a bottom-sheet help dialog
with per-platform instructions (Android Chrome, iOS Safari, desktop
Chrome, Firefox) on how to re-enable location permission
- "Got it" dismisses and resets the error state so the button returns
to normal for a fresh attempt
- handleNearMe still fires normally on first tap (not denied state)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
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>
- 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>
- SasquatchGuide now dispatches 'sasquatch:panelchange' event when panel opens/closes
- MapPage listens to the event and hides the Add Spot FAB (with scale-down animation)
whenever the Sasquatch panel is open — no more overlap
- FAB reappears with smooth opacity + scale transition when panel closes
- Added a visible close (X) button to the Sasquatch panel header alongside the existing
minimize button — gives users a clear way to fully dismiss the panel
- Sasquatch panel bottom edge stays 10px above the nav bar on all screen sizes
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
CRITICAL:
- Near Me button: completely rewritten — was silently discarding ALL errors. Now shows
toast for denied, timeout, unavailable with actionable copy. Button shows red LocateOff
icon when denied so user sees the state at a glance. Raised to 44×44px (Apple minimum).
- Map height on mobile: switched to CSS variable system (--header-mobile, --bottom-nav)
for pixel-accurate height. Added Layout noPadding prop so the map page gets full bleed
without the 80px bottom-padding fighting the map container calc.
- Layer toggle pills: raised from 28×28 to 40×40px — was below touch target minimum
- Right-side control column: now hides when spot bottom sheet is open (was always visible)
iOS/Directions:
- SpotDetailPage directions button: now detects Apple platform and opens Apple Maps on
iOS/macOS, Google Maps everywhere else (was always Google Maps)
- Added aria-label to directions button
Accessibility:
- LoginDialog: was missing DialogTitle, causing console errors on every login open
- CameraCapture: fallback dialog was missing DialogTitle (sr-only)
- CommandDialog: added sr-only DialogTitle
Layout:
- SpotDetailPage: reduced top padding mobile pt-4 (was py-8 = 32px wasted space)
- Directions popover: centered in viewport (top-1/2 left-1/2) with zoom-in animation,
never clips behind Sheet stacking context
CSS:
- Added --header-mobile and --bottom-nav CSS variables for consistent calculations
- Added .page-fullbleed helper
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- 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>
- Logged-out state: proper hero with value prop copy instead of bare login form
- Hero card preview: full-width dark stage with theme-matched glow behind card
- Card slightly larger (360x227) with depth shadow
- Top tab bar: My Card / Badges (with count) / Award — replaces buried sections
- Single-column max-w-2xl layout — no more awkward 2-col grid on mobile
- Primary CTA: one big Share button, Print/Save/Copy as secondary 3-col row
- Card type as prominent 2-up tile choice, not a buried dropdown-style control
- Theme picker: 6-up square grid with emoji + label, ring on active
- Rides For + Sponsors side by side (saves vertical space)
- Stats as interactive tiles showing the actual numbers
- Skills as pill grid with live counter (X/6 selected)
- Badges tab: shows ReceivedBadgesPanel (was previously never rendered)
- Award tab: inline form, no dialog — cleaner flow, redirects to Badges on success
- AwardBadgeDialog kept for backward compat but Award tab uses AwardBadgeInline
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Swap OSM tiles for CartoDB Dark Matter (near-black bg, neon markers pop)
- Add floating Near Me button (top-right, crosshair icon, flies to user location)
- Add SpotBottomSheet: tap a marker → drawer slides up from bottom with
spot thumbnail, title, location, type badges, author, View Details + Directions CTAs
(map stays fully visible behind the sheet)
- Add flyTo prop to SpotMap for programmatic map navigation
- Add floating layer filter pills (spots/shops/gear/tours) on map surface
bottom-left, always visible, no toolbar required
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Replace scattered filter controls (search + Select dropdowns + ScrollArea chips + TrustFilter dropdown + SortDropdown) with the same unified toolbar + filter drawer pattern used on /lab
- Sticky toolbar: Events/Tours tab toggle with live counts, Upcoming/Past sub-tabs (events only) with counts, inline search + city location inputs, filter button with active count badge
- Responsive filter drawer: bottom sheet on mobile, right side panel on desktop (md:)
- Filter drawer: Sort pills, Trust Level inline pills (no nested dropdowns), Event Type chips (events) or Tour Category + Skill Level chips (tours) — all in one place
- Active filter chips row: selected event types as dismissible pills, sort/trust badges, "Clear all" — only visible when filters are active
- CreateFAB: replaces plain Link button with a dropdown for both "Create Event" and "Create Tour" from any view
- Unified EmptyState component with context-aware "Clear Filters" vs. "Create" CTA
- Dedicated location filter input split from general search (City field)
- Results count shown below toolbar, no empty-height gaps while loading
- Removed: Input, ScrollArea, ScrollBar, Select, Tabs, TabsList, TabsContent, TabsTrigger, TrustFilter, SortDropdown imports (all replaced inline)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Add dedicated City location input field in toolbar alongside search (splits "Name/bio" and "City" search into two focused inputs)
- Fix Filter drawer responsiveness: bottom sheet on mobile, right side panel on desktop (md:)
- Replace TrustFilter compact dropdown in drawer with inline pill buttons (no nested dropdowns)
- Add active filter chips row in toolbar showing selected skills as dismissible pills, sort/trust badges, and "Clear all" button — only visible when filters are active
- Tab count badges now always show (including zero) to give clear feedback
- Fix results count row: remove empty min-h gap, now only renders when not loading
- Search now also matches against genUserName fallback for pubkey-derived names
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
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>
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>
- Removed SkateGlobe component from view toggle
- ViewMode back to 'map' | 'list', defaulting to 'map'
- Removed Globe import, selectedGlobeMarker state, and all globe render blocks
- Flat Leaflet map loads as default again
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
## 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>
- 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>
- 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>
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>
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>
- "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>
- "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>
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>
- 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>
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>
- 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>
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>
- 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>
- 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>
- 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>
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>
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>
- 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>