useEventStats now accepts an optional event object. For addressable
events (kinds 30000-39999), it queries kind 30384 stats by the a-tag
coordinate instead of kind 30383 by event ID, so reply/reaction counts
display correctly for badge collections, badge definitions, articles,
and other addressable event types.
All 8 call sites updated to pass the event object.
The same stripes + glitch icon + title + npub + reason layout was
duplicated across EmbeddedVanishCard, EmbeddedVanishPost,
VanishEventContent compact mode, and an inline version in NoteCard's
threaded branch. Extract a single VanishCardCompact component and
replace all four copies with it.
Net reduction of 76 lines.
The vanish layout (kind 62) in NoteCard didn't handle the threaded/
threadedLast props, so when rendered as a parent in AncestorThread,
it used the standard feed card layout where the caution stripes are
siblings of the bordered container — visually appearing to escape
above and below the card.
Add a dedicated threaded branch for vanish cards that:
- Uses the avatar column + connector line pattern like other threaded cards
- Places the stripes INSIDE the bordered container (as top/bottom bands)
so they stay visually contained within the card boundary
The IndexedDB profile cache (d9e943d0) introduced a regression where
parseAuthorEvent() — which runs the full Zod n.metadata().parse() chain —
was called synchronously on every render via TanStack Query's initialData
option. With 50+ NoteCards in a feed and no React.memo, this caused ~970ms
of Zod validation + ~1.3s of Nostrify core overhead per tab navigation.
Fix by:
1. Storing pre-parsed metadata in ProfileCacheEntry so useAuthor can
provide initialData without re-running Zod on every render
2. Wrapping NoteCard in React.memo to prevent re-renders when props
haven't changed (event objects are referentially stable from the
query cache)
Implement a visually striking display for kind 62 vanish events with
animated caution stripes, glitch effects, and a grand exit aesthetic.
Since the user has nuked their account, the npub is shown instead of
profile info. Supports both compact feed cards and full detail view
with comments and sharing.
Wrap GitRepoCard, PatchCard, PullRequestCard, and CustomNipCard content
in rounded-2xl bordered containers matching the quote post / embedded
note visual pattern. Add KIND_HEADER_MAP entries for all four dev kinds
(30617, 1617, 1618, 30817) so the feed shows activity labels like
'shared a repository', 'submitted a patch', 'opened a pull request',
and 'proposed a NIP' above each post.
- 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
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.
Implement NIP-53 staleness heuristic (24h threshold) to mark abandoned
status=live streams as ended. Fetch all streams globally and filter
follows client-side (including p-tag participants for streaming services).
Add Live/Planned/Past pill tabs to the streams strip on /videos.
Also fix pre-existing bug where the volume icon showed unmuted while the
video was actually muted (autoplay requires muted), by syncing the
initial muted state from the media element in usePlayerControls.
Closes#88
For kind 30000 (lists) and 39089 (starter packs), replace the prominent
avatar + bold name header with a content-first layout: the list title,
description, and member avatars are shown as the hero, followed by a
subtle small author attribution line (tiny avatar, muted text, timestamp).
This applies to both normal and threaded layouts, as well as compact/embed
mode used in quote posts and inline embeds.
- avatar.tsx: consolidate to single isEmojiShape boolean, fix AvatarFallback using same logic as Avatar
- avatarShape.ts: accept NostrMetadata directly in getAvatarShape (no cast needed)
- Remove 'as Record<string, unknown>' casts from all ~50 call sites
- Replace 'circle' magic string with empty string in form defaults and parseShape
- Use isValidAvatarShape instead of string comparison in save logic
- ProfileCard: extract IIFE overlay style into useMemo, use isEmojiShape throughout
Resolve merge conflicts from badges, books, help, and emojis rename.
Fix biome lint: add type="button" to all buttons, use template literals,
format new components with project conventions.
- New 'shape' property on kind 0 profile metadata with 7 predefined shapes:
circle, triangle, inverted-triangle, hexagon, star, inverted-star, hexagram
- Avatar component updated with clip-path support for non-circle shapes
- Visual shape picker added to both EditProfileForm and ProfileSettings
- Shape applied across all 49 avatar render sites in the app
- New UserAvatar wrapper component for future simplified avatar rendering
- Unknown shape values gracefully fall back to circle (forward compatible)
Implement badge definitions (kind 30009) and profile badges (kind 30008)
as a new Whimsy content type with feed page, NoteCard rendering, sidebar
entry, dedicated detail page with awardee list, and a Badges core profile
tab showing a user's accepted badges with links to badge definitions.
On desktop the URL bar and 'Copy Link' in the More menu already
cover sharing. The native share sheet is the unique value on mobile,
so hide the button at the sidebar breakpoint (900px+).
Add a dedicated Share button using the Web Share API with clipboard
fallback across three locations:
- NoteCard action bar (between Zap and More)
- PostDetailPage action bars (both compact and expanded views)
- ProfilePage header (between More and Zap buttons)
On mobile and supported desktop browsers, tapping Share opens the
native OS share sheet with all installed apps (Twitter, WhatsApp,
etc.). On unsupported browsers, falls back to copying the link to
clipboard with a toast confirmation.
Closes#39
Filter out undefined/empty pubkeys when extracting reply-to authors
from event p tags. Some events have malformed p tags without a pubkey
value, causing nip19.npubEncode to throw 'hex string expected, got
undefined'. Added filtering in both NoteCard (source) and ReplyContext
(defensive guard).
New 'Development' section under Other Stuff in Content Settings that
aggregates developer-focused Nostr content: git repository announcements
(kind 30617), patches (kind 1617), pull requests (kind 1618), custom
NIP proposals (kind 30817), and app submissions (kind 31733).
Adds /dev route using KindFeedPage with external links to Gitworkshop
and NostrHub. Five new card components render each kind in the feed.
The composer preview tab was missing webxdc embeds because it manually
rendered only videos and audio, while webxdc rendering lived exclusively
inside NoteCard. Extract NoteMedia into a shared component and
ImetaEntry/parseImetaMap into a shared utility, then wire them into the
ComposeBox preview with proper imeta tag generation on the mock event.
The original border-b border-border was removed from NoteCard in 9bd646b
for a photos overlay feature that no longer uses NoteCard. This forced
every container to add divide-y divide-border as a workaround, but
several pages were missed (Bookmarks, Events, Trends, Search, etc).
Restore NoteCard's self-bordering and remove the now-redundant divide-y
wrappers. Existing overrides (EmbeddedNaddr !border-b, Notifications
border-0) continue to work correctly.
Resolve conflicts in AppRouter, NoteCard, sidebarItems, and PostDetailPage
by keeping both music/podcast features and main's additions (books,
calendar events).
Add min-h-10 and flex items-center on the content side so the text line
vertically centers against the size-10 emoji bubble. Bump inline avatar
from size-5 to size-6 for better visual balance.
Replace the standalone emoji bubble with the user's normal avatar plus a
small reaction emoji badge at the bottom-right corner. This keeps the
avatar column aligned with other threaded posts while still making the
reaction visually distinct.
Reaction events now display with a prominent emoji bubble, small inline
avatar, author name, and 'reacted' label — making them visually distinct
from regular posts in both thread ancestors and standalone detail views.
Implement full calendar event rendering, dedicated events page, and
RSVP functionality for NIP-52 calendar events:
Registration:
- FeedSettings: showEvents + feedIncludeEvents toggles
- extraKinds: kind 31923 registered as 'events' in social section
with link to plektos.app for event creation/management
- Sidebar: Events entry with CalendarDays icon at /events
- Onboarding: added to content picker, pre-checked by default
Components:
- CalendarEventContent: rich card with cover image, title, date/time,
location, description, participant count, hashtags. Handles both
kind 31922 (date-based) and 31923 (time-based) with Intl formatting
- CalendarEventDetailPage: full detail view with RSVP UI (Going/Maybe/
Can't Go buttons + optional note), attendee sections with avatar
stacks, participant roles, share button, zap support
- RSVPAvatars: reusable overlapping avatar stack with tooltips
Hooks:
- useEventRSVPs: query kind 31925 RSVPs for an event, deduplicate by
author, group by status (accepted/declined/tentative)
- useMyRSVP: check current user's RSVP status for an event
- usePublishRSVP: publish kind 31925 RSVP with cache invalidation
Pages:
- EventsFeedPage: two-tab layout — Upcoming (calendar events from
follows/global with future-first sorting) and Activity (social feed
showing 'Alice is going to [Event]' from followed users' RSVPs)
Wiring:
- NoteCard: isCalendarEvent detection + CalendarEventContent dispatch
- PostDetailPage: routes kind 31922/31923 to CalendarEventDetailPage
- AppRouter: /events route
Implement recording, publishing, and rendering of NIP-A0 voice messages:
- VoiceMessagePlayer: compact waveform bar player when imeta waveform
data is available, falls back to existing AudioVisualizer sinewave
- useVoiceRecorder: MediaRecorder hook with real-time amplitude capture,
60s max duration, and waveform sampling for NIP-A0 imeta tags
- ComposeBox: mic button in toolbar with recording UI (live waveform,
timer, cancel/send controls), publishes kind 1222 (root) or 1244
(NIP-22 reply) with imeta waveform and duration fields
- NoteCard + PostDetailPage: render kind 1222/1244 via VoiceMessagePlayer
- Feed settings: voice messages registered in Other Stuff > Media with
feed toggle (feedIncludeVoiceMessages), defaults to off
When replying to your own post, the compose box excludes your pubkey
from p tags. This left self-replies with no 'Replying to' context,
making them look like top-level posts. Now falls back to the parent
e-tag author hint or the event author itself so self-replies always
show thread context.
Closes#3