Moved the Suspense boundary from App.tsx (which wrapped the entire app)
into MainLayout around just the Outlet and right sidebar. The left
sidebar, mobile nav, and mobile top bar now persist while lazy page
chunks load. A skeleton fallback fills the content area during loading.
MainLayout was rendered inside each page component, causing the entire
layout (sidebar, mobile nav, drawer) to be destroyed and recreated on
every route change. This made navigations feel like full page refreshes.
Restructured to use a React Router layout route with <Outlet />, so
MainLayout persists across navigations and only the page content swaps.
Pages that need custom layout options (showFAB, rightSidebar, etc.) use
the new useLayoutOptions hook to configure the persistent layout.
Use getProfileUrl() for all profile navigation instead of the static
/profile path. Compose box avatar, sidebar Profile link, sidebar account
popover, and mobile drawer all now link directly to the user's profile.
The /profile route becomes a redirect for backward compatibility.
- 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.
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
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.
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.
- 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.
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.