Badge Shop (/shop) now has 3 tabs following the ThemesPage pattern:
- Shop: admin badge grid with categories, search, prices (unchanged)
- Follows: NoteCard feed of badge events from followed users
- Global: NoteCard feed of all badge events
The standalone BadgesFeedPage is deleted — its content now lives as
the Follows/Global tabs inside the Shop page.
- Add useBadgeFeed hook (modeled on useThemeFeed) for infinite scroll
- Remove 'badges' sidebar item, keep 'shop' and 'achievements'
- Add 'badges' → 'shop' sidebar ID migration so existing users
who had Badges in their sidebar seamlessly get Badge Shop instead
- Redirect /badges → /shop for backward compatibility
- Add quick-action links to Create Badge and My Badges on Shop tab
Achievements are now queried as kind 30009 badge definitions from the
admin pubkey with t:achievement tag, grouped by category t-tags.
Removed the static achievementCatalog.ts - the admin publishes all
achievement badges to relays and the client discovers them dynamically.
Add full badge system with achievements, shop, creation, and management:
- 11 new hooks for badge queries, mutations, and DVM claims
- 4 new pages: Achievements, Badge Shop, Badge Create, Badge Manage
- 4 new shared components: BadgeShowcaseGrid, BadgeThumbnail, BadgeTierPill, AwardBadgeDialog
- Badge showcase on ProfileCard and ProfileHoverCard
- Badge award notifications (kind 8) in notification feed
- Enhanced BadgeDetailContent with accept/award/buy actions
- Achievement catalog (73 achievements across 8 categories)
- Shop category system with admin-issued badge support
- Sidebar items for Achievements and Badge Shop
- CSS animations for badge glow and confetti effects
Built entirely on standard NIP-58 (kinds 30009, 8, 30008) with
NIP-90 DVM for achievement verification and NIP-57 zaps for shop.
Replace three separate per-hostname databases (nostr-nip05-cache-*,
nostr-profile-cache-*, nostr-dm-store-*) with one shared 'ditto'
database containing three object stores: nip05, profiles, messages.
A single db.ts module manages the connection and upgrade logic, and
caches the promise so only one connection is created per page lifetime.
Profile events (kind 0) are now persisted in IndexedDB and hydrated into
memory before React renders. useAuthor seeds TanStack Query with
initialData from the cache so names, avatars, and metadata render
instantly on repeat visits instead of showing skeletons.
Cache writes happen from three sites: useAuthor (single profile fetch),
useAuthors (batch fetch), and useFeed (community feed metadata prefetch).
Only newer events overwrite older ones to prevent out-of-order downgrades.
Staleness tiers match NIP-05: < 5 min fresh, 5 min - 7 d background
refetch, > 7 d expired (skeleton until fresh data arrives).
Successful NIP-05 resolutions are now persisted in IndexedDB with a
lastVerified timestamp. On subsequent page loads the cache is hydrated
into memory before React renders, so TanStack Query receives initialData
and skips the pending state entirely — verified NIP-05 names appear
instantly instead of showing a skeleton placeholder.
Failed lookups are not persisted. Stale entries are evicted when the
domain responds but the identifier is no longer present.
- Add min-height placeholder to InlineImage to prevent layout shifts when
images load from 0px to natural height
- Remove 60s refetchInterval from useFeed that caused all pages to re-sort
in the background, triggering full feed re-renders
- Pre-filter deprecated/hidden events at Feed level (shouldHideFeedEvent)
instead of returning null inside NoteCard after hooks have already run
- Stabilize author info skeleton/loaded height with consistent min-h-[42px]
to prevent cascading layout recalculations
Display NIP-85 follower count next to the existing following count on
profiles. Clicking the count opens a paginated modal that fetches
followers via kind:3 #p queries with infinite scroll.
Render kind 32267 software application events with app icon, name,
summary, platform badges, screenshots, and action buttons in both
feed (NoteCard) and detail (PostDetailPage) views. Add useAddrEvent
fallback to wss://relay.zapstore.dev for kinds 32267/30063 so app
events are discoverable even when not on the user's relay set.
When the weather API reports precipitation for a country, render a
canvas-based particle overlay: animated raindrops for rain/drizzle/
thunderstorm codes, and gently drifting snowflakes for snow codes.
Intensity (light/moderate/heavy) scales particle count and speed.
The overlay is pointer-events-none at z-[100] so it doesn't block
interaction.
When a quoted event fails to load, the 'this post could not be loaded'
indicator is now clickable, navigating to the event's page. Constructs
nevent1 for regular events and naddr1 for addressable events, including
relay hints and author pubkey when available from the parent event's tags.
Use the free Open-Meteo API to show real-time weather (temperature,
humidity, wind, conditions) on country and subdivision pages. Weather
is fetched based on capital city / major city coordinates and cached
for 15 minutes.
The footer links (About, Docs, Privacy, Edit with Shakespeare) were
showing in both the left sidebar and right sidebar on desktop. Remove
from LeftSidebar since it already appears in MobileDrawer for mobile
and in the right sidebars for desktop.
Closes#131
Remove the Messages tab and add a Feed tab (using PlanetIcon) as the
leftmost item in the mobile bottom navigation. Feed highlights when
on /feed or /.
Allow users to upload their own .woff2/.woff/.ttf/.otf font files via
Blossom, which are immediately applied and stored with the theme. The
uploaded font URL is preserved when publishing themes to Nostr (via the
existing f tag in the theme event schema).
Add the attribute to the file input in ComposeBox and update
the onChange handler to iterate over all selected files, uploading each
one via handleFileUpload, enabling users to select and attach multiple
files at once.
- Fix color moments rendering long text as emoji overlay; only render
single emoji (≤2 code points) as palette overlay
- Replace MapPin with ChestIcon on geocache name
- Add geohash pill badge to geocache type/size row, linking to /g/ route
- Truncate geohash display to 5 characters
- Add /g/:geohash route with GeotagPage backed by shared TagFeedPage
- DRY up HashtagPage and GeotagPage into shared TagFeedPage component
- Extend useInterests to support g tags for geohash following
- Add geotag feed tabs to home feed with MapPin icon
Break the dialog into 3 focused steps (Scope, Details, Confirm)
with animated progress indicators, dramatic destructive color
treatment, custom scope selection cards, and a centered final
confirmation with type-to-confirm input.
Implement kind 62 event publishing to allow users to request
permanent deletion of their data from relays. Includes global
(ALL_RELAYS) and targeted (specific relays) modes with a
multi-step confirmation dialog in Advanced Settings.