From 0a41806d7ed1afc7a58ec89320e3e7d41bd72019 Mon Sep 17 00:00:00 2001 From: nrobi144 Date: Mon, 30 Mar 2026 13:33:18 +0300 Subject: [PATCH] =?UTF-8?q?fix(desktop):=20feed=20loading=20issues=20?= =?UTF-8?q?=E2=80=94=20missing=20events,=20broken=20profile=20navigation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Profile feed now includes reposts (kind 6/16), not just text notes - Metadata consume returns false to avoid wasteful null note lookups - Feed subscription limits raised from 50 to 200 for global/following - Thread replies subscription fetches NIP-22 comments (kind 1111) - Cache now handles CommentEvent for thread display - Wire onNavigateToThread through UserProfileScreen so tapping notes opens threads Co-Authored-By: Claude Opus 4.6 (1M context) --- .../desktop/cache/DesktopLocalCache.kt | 25 ++++++++++++++++++- .../desktop/feeds/DesktopFeedFilters.kt | 25 +++++++++++-------- .../desktop/subscriptions/FeedSubscription.kt | 18 ++++++++++--- .../amethyst/desktop/ui/UserProfileScreen.kt | 2 ++ .../desktop/ui/deck/DeckColumnContainer.kt | 3 +++ 5 files changed, 59 insertions(+), 14 deletions(-) diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/DesktopLocalCache.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/DesktopLocalCache.kt index f0ad8611b..6fbf9c266 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/DesktopLocalCache.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/cache/DesktopLocalCache.kt @@ -39,6 +39,7 @@ import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent import com.vitorpamplona.quartz.nip18Reposts.RepostEvent import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull +import com.vitorpamplona.quartz.nip22Comments.CommentEvent import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent import com.vitorpamplona.quartz.nip47WalletConnect.events.LnZapPaymentRequestEvent @@ -173,7 +174,7 @@ class DesktopLocalCache : ICacheProvider { when (event) { is MetadataEvent -> { consumeMetadata(event) - true + false // metadata updates User, not Note — skip event stream } is TextNoteEvent -> { @@ -212,6 +213,10 @@ class DesktopLocalCache : ICacheProvider { consumeBookmarkList(event) } + is CommentEvent -> { + consumeComment(event, relay) + } + else -> { false } @@ -235,6 +240,24 @@ class DesktopLocalCache : ICacheProvider { return true } + /** + * Consumes a kind 1111 comment event (NIP-22). + * Like text notes but uses BaseThreadedEvent reply structure. + */ + private fun consumeComment( + event: CommentEvent, + relay: NormalizedRelayUrl?, + ): Boolean { + val note = getOrCreateNote(event.id) + if (note.event != null) return false + val author = getOrCreateUser(event.pubKey) + val repliesTo = event.tagsWithoutCitations().map { getOrCreateNote(it) } + note.loadEvent(event, author, repliesTo) + relay?.let { note.addRelay(it) } + repliesTo.forEach { it.addReply(note) } + return true + } + /** * Consumes a kind 7 reaction event. * Links reaction to target notes via e-tags and a-tags. diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/feeds/DesktopFeedFilters.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/feeds/DesktopFeedFilters.kt index 6aac787c9..a9d4cdead 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/feeds/DesktopFeedFilters.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/feeds/DesktopFeedFilters.kt @@ -34,7 +34,10 @@ import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent -private fun isFeedNote(event: com.vitorpamplona.quartz.nip01Core.core.Event?): Boolean = event is TextNoteEvent || event is RepostEvent || event is GenericRepostEvent +private fun isFeedNote(event: com.vitorpamplona.quartz.nip01Core.core.Event?): Boolean = + event is TextNoteEvent || + event is RepostEvent || + event is GenericRepostEvent private fun List.deduplicateReposts(): List = distinctBy { note -> @@ -132,7 +135,7 @@ class DesktopThreadFilter( } /** - * Profile feed: all kind 1 notes by a specific pubkey. + * Profile feed: text notes + reposts by a specific pubkey. */ class DesktopProfileFeedFilter( private val pubkey: HexKey, @@ -140,19 +143,21 @@ class DesktopProfileFeedFilter( ) : AdditiveFeedFilter() { override fun feedKey(): String = "profile-$pubkey" + private fun isProfileNote(note: Note): Boolean { + val event = note.event ?: return false + return note.author?.pubkeyHex == pubkey && isFeedNote(event) + } + override fun feed(): List = cache.notes - .filterIntoSet { _, note -> - note.event is TextNoteEvent && note.author?.pubkeyHex == pubkey - }.sortedWith(DefaultFeedOrder) + .filterIntoSet { _, note -> isProfileNote(note) } + .sortedWith(DefaultFeedOrder) + .deduplicateReposts() .take(limit()) - override fun applyFilter(newItems: Set): Set = - newItems.filterTo(HashSet()) { - it.event is TextNoteEvent && it.author?.pubkeyHex == pubkey - } + override fun applyFilter(newItems: Set): Set = newItems.filterTo(HashSet()) { isProfileNote(it) } - override fun sort(items: Set): List = items.sortedWith(DefaultFeedOrder) + override fun sort(items: Set): List = items.sortedWith(DefaultFeedOrder).deduplicateReposts() override fun limit(): Int = 1000 } diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/subscriptions/FeedSubscription.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/subscriptions/FeedSubscription.kt index a7d4a633a..589e500cf 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/subscriptions/FeedSubscription.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/subscriptions/FeedSubscription.kt @@ -37,7 +37,7 @@ enum class FeedMode { */ fun createGlobalFeedSubscription( relays: Set, - limit: Int = 50, + limit: Int = 200, onEvent: (Event, Boolean, NormalizedRelayUrl, List?) -> Unit, onEose: (NormalizedRelayUrl, List?) -> Unit = { _, _ -> }, ): SubscriptionConfig = @@ -55,7 +55,7 @@ fun createGlobalFeedSubscription( fun createFollowingFeedSubscription( relays: Set, followedUsers: List, - limit: Int = 50, + limit: Int = 200, onEvent: (Event, Boolean, NormalizedRelayUrl, List?) -> Unit, onEose: (NormalizedRelayUrl, List?) -> Unit = { _, _ -> }, ): SubscriptionConfig? { @@ -121,9 +121,21 @@ fun createThreadRepliesSubscription( subId = generateSubId("thread-${noteId.take(8)}"), filters = listOf( + // Kind 1 replies via lowercase e-tag (NIP-10) FilterBuilders.byETags( eventIds = listOf(noteId), - kinds = listOf(1), // TextNoteEvent + kinds = listOf(1), + limit = limit, + ), + // Kind 1111 comments via lowercase e-tag (reply parent) or uppercase E-tag (root) + FilterBuilders.byTags( + tags = mapOf("e" to listOf(noteId)), + kinds = listOf(1111), + limit = limit, + ), + FilterBuilders.byTags( + tags = mapOf("E" to listOf(noteId)), + kinds = listOf(1111), limit = limit, ), ), diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/UserProfileScreen.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/UserProfileScreen.kt index 0c9582151..914f6fcbd 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/UserProfileScreen.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/UserProfileScreen.kt @@ -121,6 +121,7 @@ fun UserProfileScreen( onBack: () -> Unit, onCompose: () -> Unit = {}, onNavigateToProfile: (String) -> Unit = {}, + onNavigateToThread: (String) -> Unit = {}, onNavigateToArticle: (String) -> Unit = {}, onZapFeedback: (ZapFeedback) -> Unit = {}, ) { @@ -796,6 +797,7 @@ fun UserProfileScreen( onReply = onCompose, onZapFeedback = onZapFeedback, onNavigateToProfile = onNavigateToProfile, + onNavigateToThread = onNavigateToThread, onImageClick = { urls, index -> lightboxState = LightboxState(urls, index) }, diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/DeckColumnContainer.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/DeckColumnContainer.kt index 8d8f01805..a72032acd 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/DeckColumnContainer.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/deck/DeckColumnContainer.kt @@ -296,6 +296,7 @@ internal fun RootContent( onBack = {}, onCompose = onShowComposeDialog, onNavigateToProfile = onNavigateToProfile, + onNavigateToThread = onNavigateToThread, onNavigateToArticle = onNavigateToArticle, onZapFeedback = onZapFeedback, ) @@ -325,6 +326,7 @@ internal fun RootContent( onBack = {}, onCompose = onShowComposeDialog, onNavigateToProfile = onNavigateToProfile, + onNavigateToThread = onNavigateToThread, onZapFeedback = onZapFeedback, ) } @@ -426,6 +428,7 @@ internal fun OverlayContent( onBack = onBack, onCompose = onShowComposeDialog, onNavigateToProfile = onNavigateToProfile, + onNavigateToThread = onNavigateToThread, onNavigateToArticle = onNavigateToArticle, onZapFeedback = onZapFeedback, )