- Add webxdcMeta.ts utility to extract name from manifest.toml and
icon (icon.png/icon.jpg) from the .xdc ZIP using fflate
- Upload extracted icon to Blossom during file attach
- Add webxdc_name and webxdc_icon properties to imeta tags
- Parse webxdc_name/webxdc_icon in NoteCard and PostDetailPage
- Show app icon and name in WebxdcEmbed launch card, remove filename
and 'Stateful · shared session' text
Shows '<cards icon> <name> shared a deck' above magic deck cards in the
feed, matching the pattern of treasure/stream action headers. Suppressed
when the card is already showing a repost header to avoid double headers.
Replace Image icon with Palette for the list/visual toggle button.
Add MagicDeckContent to PostDetailPage kind dispatch so deck events
show their full content when viewed as a post detail (naddr route).
Replace ScrollArea with overflow-y-auto for reliable scrolling in both
list and visual views. Clicking any card (in either view) opens a
full-screen lightbox showing the card image from Scryfall at large size,
with prev/next navigation via arrows, keyboard, and swipe gestures.
Shows card name and foil indicator in the lightbox header.
Decklist box now has a List/Visual toggle in the header. Visual mode
renders a 4-column grid of card images loaded from Scryfall's API using
set/collector_number for exact printings or card name as fallback.
Includes quantity badges, foil shimmer overlay, and graceful fallback
to card name text when images fail to load.
Merge format badges, card count, and sideboard count onto one line.
Replace expand/collapse with a ScrollArea box (max 240px) containing
the full decklist with sideboard inline below a separator header.
Replace Layers icon with custom CardsIcon (stacked cards SVG) across all
navigation and settings UIs. Filter out events tagged t:unlisted for kind
37381 (magic decks) and t:hidden for kind 37516 (geocaches) in NoteCard.
Support kind 37381 addressable events for MTG deck sharing per NIP-MG spec.
Includes MagicDeckContent component with card list rendering, format/archetype
badges, commander/companion display, sideboard, foil indicators, and
expand/collapse for large decks. Adds /decks route and sidebar navigation.
- Create useWebxdc hook bridging the Webxdc API with Nostr kind 4079 state updates
- Create WebxdcEmbed component with launch-on-click UX for sandboxed iframe rendering
- Update ComposeBox to accept .xdc files and generate imeta tags with webxdc UUID
- Update NoteCard and PostDetailPage to detect and render webxdc attachments
- Skip .xdc URLs in NoteContent to avoid rendering them as raw links
Stream events (kind 30311) now render properly in mixed feeds via NoteCard
with inline LiveStreamPlayer for live streams, thumbnail for ended/planned,
title + summary clickable to open stream details, and 'is streaming' /
'streamed' action headers. LiveStreamPlayer pauses when scrolled out of view.
Show a dismissible 'Muted Content' overlay instead of rendering muted
posts directly when navigating to their detail page. Follows the same
pattern as ContentWarningGuard — children are not mounted until the
user clicks 'Show Anyway', so media and nested queries are deferred.
Cache parsed mute items in localStorage (like the follow list pattern) so
muted content is filtered immediately on page refresh without waiting for
the relay fetch + decrypt cycle. The cache is written on initial sync,
after each successful decrypt, and eagerly on add/remove mutations.
Fetches the user's encrypted mute list in parallel with the relay list
and encrypted settings during useInitialSync, then seeds both the raw
event and decrypted mute items into the TanStack Query cache. This
ensures muted content is filtered immediately on first login without
waiting for useMuteList's own delayed query.
Wire up the previously stubbed 'Mute @user' and 'Mute Conversation' menu
actions in NoteMoreMenu and ProfilePage to call useMuteList().addMute,
with toggle support for unmuting. Apply mute filtering (via isEventMuted
from muteHelpers) across all feed surfaces: main feed, profile feed,
notifications, post detail replies, search/explore stream, hashtag page,
domain feed, trending posts, and right sidebar.
- Move 'New here?' link above extension login button
- Use profile-intro.png instead of MewLogo on profile step
- Add 'More details' collapsible with banner, website, and custom fields
- Skip sync/onboarding when logged out (guard in InitialSyncGate)
- Persist sync completion in localStorage instead of sessionStorage
- Initialize sync phase synchronously to prevent flash on refresh
- Invalidate author cache after profile publish for immediate updates
Rewrite useBatchEventStats as an incremental effect-based prefetcher
that only fetches uncached event IDs. The previous useQuery approach
used a query key containing all event IDs, which changed on every
pagination and raced against a 500ms NIP-85 timeout that overwrote
good cached stats with zeros.
Also stabilize useEventInteractions query key by replacing the
nip85Stats.data object reference with a boolean to prevent
unnecessary refetches on every NIP-85 staleTime cycle.
The activeTab state and searchParams were synced bidirectionally via two
competing useEffect hooks. When navigating between tabs via sidebar links
(e.g., 'View All'), setSearchParams produced a new reference, re-triggering
the URL→state effect with a stale closure, causing an infinite loop.
Fix: derive activeTab directly from searchParams (single source of truth)
and update the URL via a callback instead of a separate state variable.
Articles (kind 30023) now have both a showArticles sidebar toggle and
feedIncludeArticles feed toggle, matching the pattern of other content
types like Vines and Streams. Added FileText icon to all icon maps.
- Add 'feed' section to EXTRA_KINDS for Posts (kind 1), Reposts (kind 6),
and Articles (kind 30023) as feed-only toggles
- Add feedIncludePosts, feedIncludeReposts, feedIncludeArticles to FeedSettings
(posts and reposts default to enabled, articles disabled)
- Add 'Notes' collapsible section in ContentSettings above 'Other Stuff'
- Feed kinds are now fully dynamic from settings instead of hardcoded
- Repost unwrapping accepts any event kind, not just kind 1
- Add article (kind 30023) card rendering with title, image, and summary
- Update all feed hooks for consistency (useFeed, useProfileFeed,
useStreamPosts, DomainFeedPage, HashtagPage)
Array order is now media (Vines, Streams), social (Polls, Packs),
whimsy (Colors, Treasures). Sidebar and mobile drawer inherit this
order automatically. Onboarding CONTENT_KINDS updated to match.
Both FeedSettingsForm and ContentSettings were missing the 'streams'
entry in their ICONS map, causing it to fall back to a Palette icon
instead of the Radio icon used everywhere else.
Close the popover via onClose callback when an emoji is selected from
the full picker. Add a 300ms cooldown (justClosedRef) to prevent the
popover from immediately reopening via hover when the picker disappears
and the cursor lands on the trigger button.
Sign up now triggers the full onboarding overlay instead of a dialog.
The flow starts with keygen -> download key (logs in) -> profile setup
(kind 0) -> then continues with the existing settings questionnaire
(theme, content, safety, follows, outro).
Added OnboardingContext with useOnboarding().startSignup() hook so any
component can trigger the signup flow. Updated LoginArea, Feed,
LeftSidebar, and FloatingComposeButton to use it instead of
SignupDialog. SignupDialog is no longer imported by any component.
If the user followed at least one pack during onboarding, render the
app tree (invisible) behind the outro overlay so React Query starts
fetching the follows feed in the background. When they tap 'Let's go',
content is already loaded. Users who skipped follows don't preload
since they'll land on the global feed which loads fast enough.
After saving settings, onboarding now checks the user's kind 3 follow
list. If empty, it shows suggested follow packs (starting with
naddr1qvzqqqyckypzpyexz3t34l966ngh5xg7u2q788hthdqmj0av3lv8s2tz9t43zt6d)
with member avatars and a Follow All button. Users with an existing
follow list skip this step.
The final step is now a warm outro thanking the user and inviting them
into the app, replacing the old spinner-only done screen.
Replace emoji icons with lucide-react components (Clapperboard, BarChart3,
MapPin, Palette, Users, Radio) in the content type picker. Remove follow
packs from the default selection for new users.
useInitialSync now fetches the kind 30078 event, decrypts it with the
user's NIP-44 signer, and applies theme/feedSettings/contentWarningPolicy
to the config before transitioning out of the sync screen. It also seeds
the TanStack query cache so useEncryptedSettings and NostrSync don't
redundantly re-fetch or re-decrypt the same event.
When a user logs in, show a sync spinner while fetching their settings
from Nostr relays. If existing settings are found (returning user on a
new device), apply them and proceed. If no settings exist (fresh account),
walk through a brief questionnaire for theme, content preferences, and
content safety policy. Uses sessionStorage to avoid re-showing on refresh.