- Top bar: logo on left (navigates to / with scroll-to-top), hamburger
menu on right (replaces search icon). Cleaner two-element layout.
- Bottom nav: remove Home tab (logo serves this purpose). New tab order
when logged in: You | Notifications | Explore | Search. When logged
out: Explore | Search.
- Drawer slides from right (matching hamburger position) instead of left.
- Search is now always visible in bottom nav for both logged-in and
logged-out users.
- Add __bookmarks as a built-in Explore item (reorderable/removable
alongside Feed, Trends, and content types) on both desktop and mobile
- Fix built-in removal bug: removing Feed/Trends no longer re-appends
them to the end of the list. Built-ins not in sidebarOrder are now
treated as explicitly removed (appear in More... to re-add)
- Add full edit mode to mobile Explore bottom sheet: pencil/Done toggle,
DnD with TouchSensor for reordering, remove buttons, add-back list
for hidden items. Normal mode unchanged (2-column grid)
- Replace Explore section in mobile drawer with Settings sub-categories
(Profile, Appearance, Wallet, Notifications, Advanced) filtered by
auth state. Remove standalone Bookmarks and Settings links from drawer
- Remove Bookmarks from desktop 'You' section (now in Explore)
- Export settingsSections and SettingsSection type from SettingsPage
Replace the settings redirect with a full inline edit experience matching
the desktop sidebar:
- Pencil icon in Explore section header toggles edit mode (Done to exit)
- Edit mode shows drag handles (GripVertical) and remove buttons (X) on
each item, with touch-friendly DnD via @dnd-kit TouchSensor (150ms
delay to distinguish scroll from drag)
- Hidden items appear below with + buttons to add them back
- Normal mode shows navigation links as before
- Edit state resets when the drawer closes
- Consistent UX between desktop sidebar and mobile drawer
- Search bar now appears in right sidebar at xl (1280px+) and stays in
left sidebar at sidebar-to-xl range (900-1279px) via xl:hidden class
- Add horizontal separator lines between right sidebar sections (Trends,
Hot Posts, New Accounts) for visual consistency with left sidebar
- Mobile drawer: replace 'Edit' text button at end of explore list with
pencil icon inline in the Explore section header, matching the desktop
sidebar pattern
- Move notifications bell icon to top-right of logo row (compact, always
visible when logged in) instead of a full nav item
- Add Feed and Trends as built-in Explore items that can be reordered and
removed alongside content-type items (Feed links to /, Trends to
/search?tab=trends)
- Default sidebar order for fresh installs: Feed, Trends, then enabled
content types
- Move Settings into the 'You' section when logged in; standalone at
bottom with separator when logged out
- Remove hover-to-remove X button on desktop (edit mode only now)
- Mobile Explore bottom sheet and drawer now include Feed and Trends
- Change mobile drawer 'Manage...' link text to 'Edit'
- Add dismissible prop to Explore bottom sheet Drawer for explicit
close-on-overlay-tap behavior
- Extend useFeedSettings with built-in item support (__feed, __trends),
hiddenItems list, and unified add/remove handling for both built-ins
and extra-kind routes
- Desktop: Remove redundant Home/Search nav items (logo serves as Home, inline
search covers both quick lookup and full search page navigation)
- Add visual section grouping with labeled separators (Explore, You, Settings)
- Replace ugly dashed 'Add' button with subtle 'More...' inline link
- Add edit mode with drag-and-drop reordering (@dnd-kit) and remove buttons
for content-type items in the Explore section
- Hover-to-remove on desktop content-type items outside edit mode
- Mobile: Replace avatar menu trigger with standard hamburger icon
- Mobile: Add search icon to top bar right side
- Mobile: Redesign bottom nav as 4-tab layout (Home, Explore, Notifications, You)
- Mobile: Explore tab opens bottom sheet with content type links (vaul Drawer)
- Mobile drawer: Replace profile header with logo, restructure to match desktop
sections, add 'Manage...' link for settings, remove redundant Profile link
- Add sidebarOrder to AppConfig for persisting custom item order
- Extend useFeedSettings hook with orderedRoutes, addToSidebar, removeFromSidebar
Add 5 new presets: Midnight, Forest, Lavender, Ocean, and Sunset. These
appear only on the /settings/appearance page. The sidebar dropdown and
mobile drawer cycle continue to show only featured presets (Black and
Pink). Active preset detection searches all presets so the label and
emoji display correctly even for non-featured presets.
Use an optimistic local cursor ref that updates immediately on markAsRead,
so newNotificationIds collapses to empty before the query cache catches up.
This prevents the NotificationsPage effect from re-calling markAsRead in a loop.
- Advance lastSyncedTimestamp when skipping due to recentlyWritten() so the
same snapshot cannot be re-applied once the write window expires
- Use pendingSettings ref in useEncryptedSettings to accumulate rapid mutations
instead of reading stale cache data
- Fix ContentSettings handleToggle to not merge against stale nostrSettings
Drop the 'Builtin' and 'Custom' section headers from the ThemeSelector
grid and the LeftSidebar dropdown so the distinction is invisible to
users. Add emoji metadata to theme presets (ThemePreset interface) and
display them in the sidebar dropdown and mobile drawer cycle button
instead of the generic Palette icon.
Restructure the theme architecture so 'light', 'dark', and 'system' are
builtin themes with build-time colors (customizable by self-hosters),
while 'black', 'pink', and future themes become presets that apply to a
single 'custom' theme slot.
Key changes:
- Theme type is now 'light' | 'dark' | 'system' | 'custom'
- builtinThemes (light/dark) and themePresets (black/pink/etc.) are
separate maps in themes.ts
- AppConfig gains optional customTheme (ThemeTokens) field
- useTheme exposes applyCustomTheme(tokens) for applying presets
- Custom theme tokens sync across devices via encrypted settings
- Backward compat: legacy 'black'/'pink' values in localStorage and
encrypted settings auto-migrate to custom + preset tokens
- All UI surfaces updated: ThemeSelector, LeftSidebar dropdown,
MobileDrawer cycle, and onboarding ThemeStep
useEffect fires after paint, causing a one-frame flash of old colors when switching themes. useLayoutEffect fires synchronously before paint, eliminating the flicker. Also keep document.documentElement.className in sync with the active theme.
Borders were on each page's <main> element, causing them to start below the top of the column. Moving sidebar:border-l and xl:border-r to the wrapper div in MainLayout ensures borders span the full column height from the very top.
- Add useOpenPost hook to centralize left/middle-click navigation logic
- Apply middle-click (aux click) support to NoteCard, hot posts sidebar, and streams feed
- Fix ComposeBox textarea using break-all instead of break-words
The HoverCard trigger now wraps only the 'Replying to' label, not the
@username mentions. This prevents the post preview hover card from
conflicting with the profile hover cards on usernames.
ProfileHoverCards on avatar, name, and @mentions inside EmbeddedNote
are now suppressed when rendered within the reply context hover card,
preventing nested hover card nesting.
Hovering over the 'Replying to @user' context on reply posts now shows
an embedded preview of the parent event, making it easy to see what is
being replied to without navigating away.
- Extract getParentEventId to shared utility (src/lib/nostrEvents.ts)
- Pass parentEventId from NoteCard to ReplyContext
- Wrap the reply context line in a HoverCard with EmbeddedNote preview
useLocalStorage's setValue captured 'state' at closure-definition time.
When multiple updateConfig calls fired before a re-render (e.g. NostrSync
and useInitialSync both calling updateConfig on startup), each call's
functional updater received the same stale state snapshot instead of the
accumulated result of previous calls — so later writes silently discarded
earlier ones.
Fixed by using React's built-in functional setState form (setState(prev =>
...)) which always receives the true current state from React's queue,
making all sequential updateConfig calls chain correctly regardless of
render timing.
Three bugs caused settings toggles to get stuck or reset:
1. useEncryptedSettings: onSuccess was updating the parsedSettings cache
under the old event ID, causing NostrSync to re-apply the stale remote
event when the new one arrived with a different ID. Fixed by using the
signed event's ID for both cache entries.
2. ContentTypeRow/SubKindRow: handleToggle was building the Nostr payload
from the local React feedSettings state, which lags behind on rapid
successive toggles. Fixed by merging against the Nostr-cached settings
(nostrSettings?.feedSettings) as the base instead.
3. NostrSync: lastSyncedTimestamp ref reset to 0 on every page reload, so
the 10-second recentlyWritten() guard was the only protection against
stale relay events overwriting local changes. Fixed by seeding the ref
with the current remote lastSync on first settings load, skipping
application of any event that isn't strictly newer than what was
already in the cache.
Replace the single tabbed Settings page with a landing menu that navigates
to dedicated sub-pages, following the Agora pattern:
- Settings landing: card-based menu with icons and descriptions
- Profile: wraps EditProfileForm (auth-gated)
- Appearance: Theme sub-tab with visual theme selector (light/dark/black/pink
+ custom placeholder) and Content sub-tab with existing content settings
- Wallet: dedicated page for NWC wallet management (auth-gated)
- Notifications: push notification enable/disable toggle (auth-gated)
- Advanced: network relays, stats source, and system settings (wallet
section removed since it has its own page now)
- FloatingComposeButton accepts kind prop: kind=1 opens compose dialog, others show 'Coming soon'
- FAB positioned sticky bottom-right within center column (visible on all screen sizes)
- Center column gets bottom padding (pb-24) to account for FAB obstruction
- LayoutContext extended with fabKind option passed through layout system
- Home feed uses kind=1, KindFeedPage passes its kind, StreamsFeedPage uses 30311
The endless spinner issue was caused by lazy loading pages without a Suspense
fallback. When users navigated to nevent URLs or other NIP-19 identifiers, the
Suspense boundary had no fallback, causing a blank screen or stuck preloader.
Changes:
- Removed all lazy() imports from AppRouter
- Removed Suspense boundary (no longer needed without lazy loading)
- All pages now load eagerly for instant rendering
- Kept the HexIdentifierPage loading skeleton fix from previous commit
This ensures users see immediate visual feedback when loading any page,
especially when navigating directly to event URLs via nevent identifiers.
When users navigate to a raw 64-char hex URL (event ID or pubkey), the app
now shows a proper loading skeleton while resolving the identifier type,
instead of returning null which caused an endless spinner due to Suspense.
- Export PostDetailShell and PostDetailSkeleton from PostDetailPage
- Use proper loading UI in HexIdentifierPage component
- Matches loading pattern used by PostDetailPage and AddrPostDetailPage