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.
- 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
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
Per NIP-18, kind 6 is specifically for reposting kind 1 text notes,
while kind 16 should be used for all other event types. Added
isRepostKind() and getRepostKind() helpers to centralize the logic,
and updated all repost-related code paths: publishing, deletion,
status checks, interactions, feed processing, notifications, and
stream/search filtering.
Defer body inline style removal until CSS variables are injected by
useApplyTheme, preventing a one-frame flash of unstyled background.
Replace preloader logo <img> with CSS mask technique matching the React
DittoLogo component, so the logo uses the theme's primary color instead
of filter:invert() which turned purple into green.
After a file uploads, decode it on a small canvas (64px wide) to
compute the natural dimensions (dim) and a 4x3 blurhash. Both are
injected into the NIP-94 tag array before storing in uploadedFileGroups,
so the published imeta tag includes them automatically.
When the container has an aspect-ratio from the dim tag, switch the
video to absolute inset-0 h-full to fill it cleanly. Fall back to
max-h-[70vh] object-cover for videos without dim data.
- Extract blurhash from imeta tags for all event kinds (non-kind-1
via the first imeta tag; kind-1 by matching the URL against imeta)
- New MediaTile component renders Blurhash canvas before the image/video
loads, falling back to a Skeleton when no blurhash is available
- Tile uses absolute positioning so the placeholder fills the square cell
- Move aspect-ratio and height constraints onto the button container so
both placeholder and image can fill it with absolute inset-0
- Pass integer decode resolution to Blurhash and size the canvas via
CSS width/height 100% instead of relying on string props
- Remove stale per-element skeletonStyle/heightStyle juggling
- Parse NIP-94 dim and blurhash fields in parseImetaMap (NoteCard + PostDetailPage)
- ImageGallery/GridImage: use dim for correct aspect-ratio skeleton sizing; render Blurhash canvas placeholder when available
- VideoPlayer: set aspect-ratio container from dim; show Blurhash canvas before first frame/poster loads
- Install react-blurhash (+ blurhash) for canvas rendering