Resolve merge conflicts from badges, books, help, and emojis rename.
Fix biome lint: add type="button" to all buttons, use template literals,
format new components with project conventions.
Replace Vite 6 (esbuild + Rollup) with Vite 8 (Rolldown), reducing
build times from ~12s to ~3.5s. Switch from the archived
@vitejs/plugin-react-swc to @vitejs/plugin-react v6 which uses Oxc
for React Refresh transforms.
Dynamically imports and initializes Plausible when the
VITE_PLAUSIBLE_DOMAIN env variable is set at build time.
When unset, the tracker code is never loaded.
Create a reusable HelpTip component that shows a (?) icon popover with
FAQ content pulled from helpContent.ts, linking to the full Help page.
Place tips in settings where normie users are likely confused:
- Network settings: Relays and Blossom Servers section headers
- Wallet settings: NWC section header and zaps empty state
- Notification settings: next to 'zaps' in push notification description
- Profile settings: next to the Lightning address field
- Relay list manager: next to 'Your Relays' header (also remove NIP-65 jargon)
- Content settings: next to subtitle mentioning Nostr content types
- Add NIP-51 Follow Sets management (useUserLists, UserListsPage at /lists)
- Add Follow Packs support (useFollowPacks) for curated people lists
- Add FeedEditModal for creating/editing home feed tabs with author scope,
kind picker, and list/pack author population
- Add AddToListDialog accessible from the note more-menu
- Unify home feed tab resolution with profile tabs via useResolveTabFilter,
supporting $follows variable expansion
- Wire People filter in Search and FeedEditModal with list/pack picker
- Extract shared ScopeToggle, ListPackPicker, parseSelectedKinds primitives
into SavedFeedFiltersEditor to eliminate duplication across three call sites
- Cache savedFeeds in AppConfig for instant tab render on load
- Strip orphaned profile-destination entries from encrypted settings on read
- Replace the raw text Input in the author filter with ProfileSearchDropdown
so users get the same typeahead + avatar dropdown as the main search bar
- Add hideCountry prop to ProfileSearchDropdown to suppress country suggestions
in contexts where they are irrelevant
- Author filter only writes to URL when a profile is explicitly selected from
the dropdown (immediate commit) — typing alone never updates the URL, so
slow per-keystroke relay queries no longer cause the filter to thrash
- Show a chip with the selected author's display name and an X to clear,
replacing the dropdown while an author is active
- Add commitAuthor / clearAuthor helpers that keep the URL param and the
display label in sync; resetFilters also clears the display label
- Add homePage config field (default: 'feed') that determines which
sidebar item renders at the '/' route
- Any sidebar item can be set as the homepage via Feed settings
- The feed is always accessible at /feed when not configured as home
- Normalize sidebar item IDs: every item follows path === '/' + id
(feed path changed from '/' to '/feed', emoji-packs renamed to emojis)
- Remove BOTTOM_NAV_ITEMS filter from MobileDrawer so all sidebar
items are visible on mobile (feed, notifications, search included)
- Mobile bottom nav dynamically reflects the configured homepage icon
- Homepage setting syncs across devices via encrypted settings
- Add emoji-packs -> emojis migration for existing sidebar orders
Install Cinzel, Nunito, Quicksand, Courier Prime, and Comic Neue as
bundled fonts and register them in the font registry. Add Unsplash
background images to every new preset (Gothic, Cottage, Candy Heart,
Midnight, Hologram, Floret, Win 95, Plush).
Sentry SDK is loaded via dynamic import() only when a DSN is configured
and error reporting is enabled, keeping it out of the initial bundle.
Users can control error reporting via a toggle and DSN field in
Advanced Settings, with the DSN synced across devices via encrypted
NIP-78 settings. The ErrorBoundary now reports fatal crashes to Sentry
with component stack context, and a beforeSend hook censors nsec
private keys before any data leaves the browser.
Fixes race condition where NBrowserSigner would throw 'Browser extension
not available' when app code accessed signer.nip44 before the extension's
content script had injected window.nostr on page reload.
Replace the bespoke PhotoSlide / PhotoCard / PhotoDetailOverlay stack (~250 lines) with a
thin extension of the existing Lightbox component. Lightbox gains bottomBar, onNextEvent,
and onPrevEvent props; ImageGallery forwards them through. PhotosFeedPage now opens a plain
Lightbox with author info + reactions injected via bottomBar, and cross-event swipe/keyboard
navigation wired through onNextEvent/onPrevEvent. Page shrinks from 626 → ~300 lines.
Integrate custom emojis from the user's kind 10030 emoji list into
the emoji picker, compose flow, reactions, and DMs:
Hook:
- useCustomEmojis: query kind 10030, extract emoji shortcode/url pairs
EmojiPicker:
- Accept customEmojis prop, render as dedicated 'Custom' tab via
emoji-mart's custom categories feature
- Changed onSelect from string to EmojiSelection union type
(native unicode | custom shortcode+url)
ComposeBox:
- Pass custom emojis to picker, insert :shortcode: on selection
- Scan content on publish for :shortcode: patterns, cross-reference
against user's emoji list, add ['emoji', name, url] tags (NIP-30)
QuickReactMenu:
- Custom emoji reactions: content=':shortcode:', with emoji tag
- Optimistic cache update includes custom emoji URL/name
DMChatArea:
- Pass custom emojis to picker, insert :shortcode: text
Settings:
- Registered as 'Custom Emojis' in Other Stuff > Whimsy section
- showCustomEmojis toggle (default true) gates all custom emoji
features — when disabled, no kind 10030 queries are made
- Fix pre-existing Bot icon missing import in sidebarItems
When viewing a kind 1111 comment thread where the root is a person
(kind 0 profile), show a compact profile preview card with avatar,
name, and NIP-05 at the top of the page, similar to how external
content previews are shown for URLs/books/countries.
- Create extraKindIcons.ts as single source of truth for extra-kind icons
- BUILTIN_ICON_COMPONENTS in SidebarNavItem covers system items only
- sidebarItemIcon() merges both maps; all pages/routes use it
- EXTRA_KINDS is now source of truth for labels and kind numbers in AppRouter, page titles, and onboarding (CONTENT_KINDS derived from EXTRA_KINDS)
- Remove themes from BUILTIN_SIDEBAR_ITEMS (it lives in EXTRA_KINDS)
- Delete dead SIDEBAR_ITEM_ICONS export
- Rename 'Theme' → 'Vibe', 'Colors' → 'Color Moments'
- Articles icon: FileText → BookOpen; Settings icon: Settings → Scroll
- Hide Vibe settings back button on non-mobile (md:hidden)
- Settings and Bookmarks page headers aligned to same structure
- Codex of Configuration opacity 40% → 60%
Add Skater, Kawaii, Grunge, MS Paint, Retro Pop, Bubblegum, and Gamer
theme presets derived from the ditto-1 aesthetic designs. Each preset
includes tailored CoreThemeColors, a display font, and a Blossom-hosted
background image.
Install and register 7 new fontsource display fonts (Permanent Marker,
Cherry Bomb One, Creepster, Silkscreen, Bungee Shade, Luckiest Guy,
Press Start 2P) in the bundled font registry so they appear in the
FontPicker for custom theme building as well.
Replace the custom WebxdcFeedPage with KindFeedPage, powered by
a new tagFilters option on useFeed/Feed/KindFeedPage. The webxdc
page now uses the same follows/global tabs, pull-to-refresh, and
NoteCard rendering as every other feed.
- useFeed: accept optional tagFilters (e.g. { '#m': [...] })
- Feed/KindFeedPage: pass tagFilters through
- FloatingComposeButton: accept onFabClick callback override
- LayoutOptions: add onFabClick for custom FAB behavior
- Remove useWebxdcFeed (no longer needed)
- Add ThemeConfig wrapper type (colors + fonts + background) replacing bare CoreThemeColors
- Sync theme event parsing/building with NIP.md spec: colors in c tags (hex), fonts in f tags, background in bg tags (imeta-style), empty content field
- Legacy backward compat: parse old JSON-in-content format and bare CoreThemeColors in localStorage/encrypted settings
- Bundle 10 curated fonts via fontsource (Inter, DM Sans, Outfit, Montserrat, Lora, Merriweather, Playfair Display, JetBrains Mono, Comfortaa, Comic Neue) with lazy dynamic imports
- Create font loader (src/lib/fontLoader.ts) for @font-face injection from remote URLs and CSS override application for title/body roles
- Rewrite FontPicker as dual title/body picker using bundled fonts instead of Google Fonts CDN
- Remove Google Fonts integration (useGoogleFont.ts, GoogleFontLoader.tsx)
- Resolve bundled font family names to CDN URLs when publishing to Nostr
- Apply profile theme fonts when visiting other users' profiles
- Update all consumers: AppProvider, useTheme, usePublishTheme, useEncryptedSettings, ThemeSelector, ThemeBuilderPage, ProfilePage, EditProfileForm, InitialSyncGate, NostrSync
Merge upstream changes with the theme settings refactor. Adds a
'Display my theme to others' toggle that publishes a kind 11667
active profile theme event when enabled and issues a kind 5 deletion
when disabled. Split /settings/appearance into /settings/theme and
/settings/content. Move theme builder to /settings/theme/edit.
Replace the tabbed Appearance settings page with two separate pages:
- /settings/theme for theme selection
- /settings/content for feed and content preferences
Move the theme builder from /settings/theme to /settings/theme/edit to
avoid the route conflict. Update all navigation links across the codebase.
Replace the 19-token ThemeTokens interface with a 4-field CoreThemeColors
for config storage, Nostr events, and encrypted settings. All other
Tailwind CSS tokens (card, muted, border, etc.) are now derived
automatically via deriveTokensFromCore().
- Add CoreThemeColors interface: { background, text, primary, secondary }
- Keep ThemeTokens as internal type for CSS variable injection
- Update builtinThemes and themePresets to store CoreThemeColors
- Add backward-compatible parsing for legacy 19-token format in Nostr
events, encrypted settings, and localStorage
- Simplify ThemeBuilderPage: remove Surface/UI color sections, just 4
color pickers with auto-derivation always on
- Update all consumers: ThemeSelector, ScopedTheme, ThemeUpdateCard,
MobileDrawer, LeftSidebar, ProfilePage, InitialSyncGate, etc.
- 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
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
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.
- 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
The FloatingComposeButton now returns null for logged-out users,
removing the floating Join CTA from mobile. The topbar Join and
other login CTAs are unchanged.