When a theme background image is active, the rounded panel containers
were visually extending into the status bar (top) and home indicator
(bottom). Safe area insets were applied inside child elements but the
outer wrapper painted behind them. Move the insets to the outer wrapper
so the panels sit entirely within the safe area.
- Add Android intent-filter with autoVerify for https://ditto.pub/*
- Create DeepLinkHandler component to handle appUrlOpen events at runtime
- Mount DeepLinkHandler in AppRouter inside BrowserRouter context
- Add public/.well-known/assetlinks.json with pub.ditto.app package and signing cert fingerprint
Addressable events (kinds 30000-39999) like lists get new event IDs when
updated, causing nevent references to break. Use naddr coordinates
(kind:pubkey:d-tag) which remain stable across updates.
Replace silent null returns in EmbeddedNote and EmbeddedNaddr with a
visible tombstone UI showing 'This post could not be loaded' with a
dashed border and icon, so users know a quote was referenced even when
the event can't be fetched from relays.
- Add 'ditto' CoreFeedTab that renders the same hot-sorted curated feed
(useInfiniteHotFeed) previously shown only to logged-out visitors
- Change Global tab default from enabled to disabled
- Update useFeedTab to default to 'ditto' instead of 'global' when logged out
- Add showDittoFeed localStorage toggle in Feed.tsx and ContentSettings.tsx
- Add Ditto Feed toggle row in ContentSettings FeedTabsSection
The icon next to the list title adds visual noise without helping. The
PartyPopper icon is kept for starter packs (kind 39089) where it has
distinct meaning.
For kind 30000 (lists) and 39089 (starter packs), replace the prominent
avatar + bold name header with a content-first layout: the list title,
description, and member avatars are shown as the hero, followed by a
subtle small author attribution line (tiny avatar, muted text, timestamp).
This applies to both normal and threaded layouts, as well as compact/embed
mode used in quote posts and inline embeds.
When a kind 30000 list is quoted, ComposeBox encodes it as a nevent
which routes through EmbeddedNote. EmbeddedNote had no kind-specific
handling, so lists appeared as generic text cards instead of the rich
card with title, member avatars, and description.
Add the same NOTECARD_KINDS check (kind 30000, 39089) that EmbeddedNaddr
already uses, delegating to NoteCard compact for these kinds.
Replace the single Share button on the list detail page with a dropdown
menu offering Share (copy link / native share), Repost (kind 16 generic
repost), and Quote post (opens composer with quoted event).
Also rename all user-facing 'Follow Sets' terminology to 'Lists' across
the UI: UserListsPage, ListDetailPage, FollowPackDetailContent,
AddToListDialog, and SavedFeedFiltersEditor.
- Group reactions, reposts, and zaps on the same post into a single condensed
row with stacked actor avatars (up to 5 + overflow count); single-actor
interactions use the original compact layout
- Deduplicate actors by pubkey within groups
- Sum zap totals across all actors in a zap group
- Add notificationPreferences per-type settings (reactions, reposts, zaps,
mentions, comments, onlyFollowing) to EncryptedSettings and Zod schema
- Per-type kind toggles and onlyFollowing applied at relay query level (kinds
array + authors filter), not client-side; query key includes kindsKey and
authorsKey so preference changes trigger a fresh fetch
- Default push notifications to enabled
- Notification settings UI matches ContentSettings exactly: section headers
with accent underline, p-4 wrapper, broken border rows (border-b on outer
wrapper div), sub-section labels, w-[52px] switch column, KindBadges
- Add 'Only from people I follow' toggle under Filter sub-section
- Filter and push notification prefs also respected in useNativeNotifications
web push subscription via stable ref
- ProfileTabEditModal: add 'People' scope option with AuthorFilterDropdown and
ListPackPicker so users can pick specific pubkeys as tab authors, matching
the filter options available on the normal search filter
- useProfileFeed: add useTabFeed hook that runs a paginated useInfiniteQuery
with full NostrFilter support (kinds, authors, NIP-50 search)
- ProfileSavedFeedContent: replace useStreamPosts with useTabFeed so custom
tab content uses cursor-based pagination (like the posts tab) instead of
streaming, fixing search-keyed queries and enabling infinite scroll
Deduplicates the kind 10063 tag parsing logic that was repeated in both
NostrSync.tsx and useInitialSync.ts. Both now call the shared function
from lib/appBlossom.ts.
Blossom servers now work exactly like relays:
- App default servers (APP_BLOSSOM_SERVERS) can be toggled on/off
- User's kind 10063 server list is fetched and synced from Nostr
- NostrSync fetches kind 10063 on login and applies it to config
- useInitialSync fetches kind 10063 in parallel on first login
- BlossomSettings UI mirrors RelayListManager with two sections:
App Blossom Servers (with enable toggle) + Your Blossom Servers
- User changes publish a kind 10063 replaceable event to Nostr
- getEffectiveBlossomServers() merges app + user servers like getEffectiveRelays()
- Migration from old blossomServers[] to blossomServerMetadata handled in AppProvider
Non-kind-1 events (articles, photos, videos, etc.) bypassed the client-side
search check via an early return, so streamed events of any extra kind passed
through regardless of the search query. Remove the early return and apply the
query filter to all kinds, also checking title/summary/subject/alt tags for
non-text events. Reply filtering still only applies to kind 1/1111.
The Protocol label used flex layout for the HelpTip icon while the other
three labels did not, causing vertical misalignment of the selects below
them. Give all four labels the same flex items-center gap-1 classes.
Also rename Platform to Protocol.
Kind 6/16 repost events were passed raw to NoteCard without extracting the
embedded original event, so reposts showed nothing. Mirrors the same
parseRepostContent + repostedBy pattern used in useFeed/Feed.tsx.
parseKindFilter returned undefined for 'all', causing useStreamPosts to fall
back to the user's enabled feed-settings kinds instead of every kind in the
kind picker list. Now kindsOverride always contains explicit kind numbers:
all picker kinds when filter is 'all', or the specific selection otherwise.
getEnabledFeedKinds only returns user-toggled extra kinds, never kind 1.
The fallback paths for videos/images/all were missing kind 1 entirely,
so searches returned no results unless the user had explicitly enabled
extra feed kinds.
- Prevent focus steal on first keystroke: the add-to-feed bookmark button was
conditionally mounted when searchQuery became non-empty, causing a DOM mutation
mid-keystroke that stole focus from the input
- Eliminate searchParams feedback loop: the state→URL sync effect had searchParams
in its dependency array, causing a cascade of extra re-renders on every keystroke
after the debounce settled
- Eliminate per-keystroke re-renders: raw input state now lives in a dedicated
SearchInput child component with its own internal debounce; SearchPage only
holds the debounced value and re-renders when typing stops, not on every character
Add BioContent component that tokenizes bio text to render URLs as
clickable links and hashtags as internal navigation links, while
preserving NIP-30 custom emoji support. Applied to ProfilePage,
ProfileHoverCard, and ProfileCard read-only bio displays.
The vine-slide-height CSS class statically subtracted 3.5rem for the
bottom nav. Added a vine-slide-height-expanded class without that
subtraction, toggled by scroll direction so the video fills the full
viewport when the bottom nav slides away.
The Vines page uses an internal snap-scroll container instead of window
scroll, so useScrollDirection (which listened to window.scrollY) never
detected any scroll movement. Add a scrollContainer option to
LayoutOptions so pages with custom scroll containers can communicate
the element to MobileBottomNav, which passes it to useScrollDirection.
Replace Feed/Home button with Profile, which shows the user's avatar at
a small size. Search and Notifications remain. Logged-out users see a
generic User icon for Profile instead of the avatar.
Bring back the mobile bottom navigation bar with Home, Notifications, and
Search buttons. The nav slides out of view when scrolling down and reappears
on upward scroll, keeping the viewport uncluttered while reading. Search
retains the popover account search sheet for fast profile lookup without
navigating away. Updated CSS height calculations for full-screen pages
(AI chat, vines, livestream) and FAB positioning to account for the nav.
Replace border/ring with a crisp drop-shadow outline (via emojiAvatarBorderStyle)
so the border follows the emoji mask shape without clipping. Shrink emoji avatars
by the border width so they match the visual footprint of bordered circular ones.
The mute list (kind 10000) was only reading encrypted content, ignoring
public tags in the event's tags array. Per NIP-51, mute items can be
stored as either public tags or private encrypted content. Most Nostr
clients store mutes as public tags, so these were being silently ignored.
Changes:
- Parse both public tags and encrypted content from kind 10000 events
- Add NIP-04 backward compatibility (detect by '?iv=' in ciphertext)
- Deduplicate items that appear in both public and private sections
- Apply same fixes to useInitialSync for login-time bootstrapping
Fixes: https://gitlab.com/soapbox-pub/ditto/-/work_items/96