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.
The body had an inline background style from the preloader that
overrode CSS variables. Now main.tsx removes the inline style
after React mounts, letting Tailwind's bg-background class take
over so theme changes apply instantly.
- Cache follow list and author profiles in localStorage for instant feed on return visits
- Prefetch profile images via <link rel=preload> for instant avatar rendering
- Replace Radix Avatar with lightweight component that renders <img> immediately
- Lazy-load route components (React.lazy) reducing main bundle from 1.7MB to 920KB
- Dynamic import hls.js (1.3MB) only when visiting stream pages
- Remove unused Buffer polyfill
- Add pre-React loading spinner in index.html outside #root for seamless handoff
- Fix 3-5s feed rerender caused by NostrSync relay/settings sync
- Fix Zod 4 strict mode rejecting feedSettings with extra/missing keys from localStorage
- Fix useLocalStorage skip update when value reference unchanged
- Reduce eoseTimeout from default to 600ms
Long unbroken names (raw npubs used as display names) in 'Replying to'
overflowed the container, pushing the page wider than the viewport and
making sticky/fixed nav elements scroll off-screen horizontally.
Truncate author names at 200px, add overflow-hidden on the container,
and shrink-0 on static text to prevent wrapping issues.
Replaces the AlarmManager-based polling (wake every 60s, open/close WebSocket
connections) with a persistent WebSocket subscription to a Nostr relay. Events
are delivered in real-time via the open connection. A partial WakeLock keeps
the connection alive through Doze mode (same approach as Signal without FCM).
- Add NotificationRelayService: foreground service with persistent WebSocket,
exponential backoff reconnection, network-aware reconnect, relay rotation
on failure, and SharedPreferences config listener
- Refactor NostrPoller into notification dispatcher with metadata caching
(SharedPreferences-based, avoids redundant WebSocket connections for
repeat authors)
- Remove NotificationService (AlarmManager scheduler) and PollAlarmReceiver
(WakeLock-based polling)
- Remove SCHEDULE_EXACT_ALARM permission (no longer needed)
- Disable web-layer background refetch on native (persistent connection
handles it)
CW media squares show a blurred placeholder with a shield icon instead
of loading the actual image/video. When policy is 'hide', CW media is
excluded from the grid entirely.
Respect content-warning tags on events. Users can choose how to handle
them in Settings > Content > Sensitive Content:
- Blur (default): Shows a grey placeholder overlay with the warning
reason in quotes. Media is not loaded until the user clicks reveal.
- Hide: Removes content-warned posts from the feed entirely.
- Show: Ignores content warnings and displays everything normally.
The setting syncs across devices via encrypted NIP-78 settings.
- Update NoteContent tests to use inline URLs (mid-sentence) instead of
end-of-line URLs that render as LinkPreview cards without accessible links
- Update mention styling assertion from text-gray-500 to text-muted-foreground
to match the actual NostrMention component classes
- Add android/ to ESLint ignores to prevent linting build output artifacts
Lock main element to viewport height on mobile with body scroll disabled,
collapsible description with author under details, zap button inline with
title, and chat input sitting directly above the bottom nav with no gaps.
Implement kind 30311 live streaming events: HLS video player (hls.js),
kind 1311 live chat with real-time subscriptions, stream detail page with
collapsible description on mobile, desktop sidebar chat, streams feed page,
and full extra-kinds integration with sidebar nav and settings.
Apply EmojifiedText to mentions inside embedded/quote posts,
embedded note author names, and all other secondary UI locations
where display names were still rendered as plain text.