From f4785d49243b3ccf687503cd013582b1749a7e9d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 17:05:38 +0000 Subject: [PATCH 1/3] fix: render NIP-34 status events instead of a blank note Kinds 1630-1633 (open/applied/closed/draft) reach the notifications tab through the p-tag filter but had no branch in RenderNoteRow, so they fell through to the text renderer. Their content is usually empty and they are not BaseThreadedEvents, so the card came out blank. Draw them as the status pill, the optional comment, and the targeted issue/patch/PR (already linked as replyTo via the marked-root e tag) quoted below. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PicvdWRJ33zh4MCfjZj2kQ --- .../commons/nip34Git/ui/GitStatusPill.kt | 2 +- .../amethyst/commons/ui/note/NoteCompose.kt | 6 ++ .../commons/ui/note/types/GitStatus.kt | 69 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/GitStatus.kt diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip34Git/ui/GitStatusPill.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip34Git/ui/GitStatusPill.kt index fafde36324..2de995c8d9 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip34Git/ui/GitStatusPill.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/nip34Git/ui/GitStatusPill.kt @@ -33,7 +33,7 @@ import com.vitorpamplona.quartz.nip34Git.status.GitStatusDraftEvent import com.vitorpamplona.quartz.nip34Git.status.GitStatusEvent import com.vitorpamplona.quartz.nip34Git.status.GitStatusOpenEvent -private fun GitStatusEvent.statusKind(): StatusKind = +fun GitStatusEvent.statusKind(): StatusKind = when (this) { is GitStatusAppliedEvent -> StatusKind.APPLIED is GitStatusClosedEvent -> StatusKind.CLOSED diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/NoteCompose.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/NoteCompose.kt index b15ce3709b..682ee75b5a 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/NoteCompose.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/NoteCompose.kt @@ -194,6 +194,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.types.RenderFhirResource import com.vitorpamplona.amethyst.commons.ui.note.types.RenderFundraiser import com.vitorpamplona.amethyst.commons.ui.note.types.RenderGeocache import com.vitorpamplona.amethyst.commons.ui.note.types.RenderGeocacheFoundLog +import com.vitorpamplona.amethyst.commons.ui.note.types.RenderGitStatusEvent import com.vitorpamplona.amethyst.commons.ui.note.types.RenderGoal import com.vitorpamplona.amethyst.commons.ui.note.types.RenderHighlight import com.vitorpamplona.amethyst.commons.ui.note.types.RenderInteractiveStory @@ -362,6 +363,7 @@ import com.vitorpamplona.quartz.nip34Git.patch.GitPatchEvent import com.vitorpamplona.quartz.nip34Git.pr.GitPullRequestEvent import com.vitorpamplona.quartz.nip34Git.pr.GitPullRequestUpdateEvent import com.vitorpamplona.quartz.nip34Git.repository.GitRepositoryEvent +import com.vitorpamplona.quartz.nip34Git.status.GitStatusEvent import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent @@ -1393,6 +1395,10 @@ private fun RenderNoteRow( ) } + is GitStatusEvent -> { + RenderGitStatusEvent(baseNote, quotesLeft, backgroundColor, accountViewModel, nav) + } + is EncryptedDmEvent -> { RenderPrivateMessage( baseNote, diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/GitStatus.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/GitStatus.kt new file mode 100644 index 0000000000..d4245938c4 --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/GitStatus.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.ui.note.types + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color +import com.vitorpamplona.amethyst.commons.model.Note +import com.vitorpamplona.amethyst.commons.nip34Git.ui.statusKind +import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.note.GitStatusPill +import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer +import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel +import com.vitorpamplona.quartz.nip34Git.status.GitStatusEvent + +/** + * Renders a NIP-34 status event (kinds 1630-1633). These usually carry an + * empty `content`, so the text fallback drew a blank note: show the new + * status as a pill, the optional comment, and the issue/patch/PR it + * targets (linked through the event's marked-`root` `e` tag) quoted below. + */ +@Composable +fun RenderGitStatusEvent( + note: Note, + quotesLeft: Int, + backgroundColor: MutableState, + accountViewModel: AccountViewModel, + nav: INav, +) { + val event = note.event as? GitStatusEvent ?: return + val kind = remember(event) { event.statusKind() } + + GitStatusPill(kind) + + if (event.content.isNotBlank()) { + Spacer(modifier = StdVertSpacer) + Text( + text = event.content, + style = MaterialTheme.typography.bodyMedium, + ) + } + + if (note.replyTo?.lastOrNull() != null) { + Spacer(modifier = StdVertSpacer) + RenderZappedPost(note, quotesLeft, backgroundColor, accountViewModel, nav) + } +} From 0e3cfb351aca6c91a2a5394051ed5282daa38527 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 18:24:01 +0000 Subject: [PATCH 2/3] fix: render NIP-34 status events as the thread's master note Opening a status event (kinds 1630-1633) in the thread view fell through NoteMaster's dispatch to the text renderer, drawing the same blank body the notifications tab did. Route it to RenderGitStatusEvent too. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PicvdWRJ33zh4MCfjZj2kQ --- .../amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt index e22773d3c0..71492e7f99 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt @@ -186,6 +186,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.types.RenderFhirResource import com.vitorpamplona.amethyst.commons.ui.note.types.RenderFundraiser import com.vitorpamplona.amethyst.commons.ui.note.types.RenderGeocache import com.vitorpamplona.amethyst.commons.ui.note.types.RenderGeocacheFoundLog +import com.vitorpamplona.amethyst.commons.ui.note.types.RenderGitStatusEvent import com.vitorpamplona.amethyst.commons.ui.note.types.RenderGoal import com.vitorpamplona.amethyst.commons.ui.note.types.RenderHighlight import com.vitorpamplona.amethyst.commons.ui.note.types.RenderInteractiveStory @@ -332,6 +333,7 @@ import com.vitorpamplona.quartz.nip34Git.patch.GitPatchEvent import com.vitorpamplona.quartz.nip34Git.pr.GitPullRequestEvent import com.vitorpamplona.quartz.nip34Git.pr.GitPullRequestUpdateEvent import com.vitorpamplona.quartz.nip34Git.repository.GitRepositoryEvent +import com.vitorpamplona.quartz.nip34Git.status.GitStatusEvent import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent @@ -1026,6 +1028,8 @@ private fun FullBleedNoteCompose( RenderGitPullRequestEvent(baseNote, makeItShort = false, canPreview = true, quotesLeft = 3, backgroundColor = backgroundColor, accountViewModel = accountViewModel, nav = nav) } else if (noteEvent is GitPullRequestUpdateEvent) { RenderGitPullRequestUpdateEvent(baseNote, makeItShort = false, canPreview = true, quotesLeft = 3, backgroundColor = backgroundColor, accountViewModel = accountViewModel, nav = nav) + } else if (noteEvent is GitStatusEvent) { + RenderGitStatusEvent(baseNote, quotesLeft = 3, backgroundColor, accountViewModel, nav) } else if (noteEvent is AppDefinitionEvent) { RenderAppDefinition(baseNote, accountViewModel, nav) } else if (noteEvent is AppRecommendationEvent) { From a9c755e67b0c75d53d3887597398e578cd9a5207 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 19:37:28 +0000 Subject: [PATCH 3/3] refactor: rename RenderZappedPost to RenderTargetNote The helper quotes whatever note an activity event acts on (its last replyTo) and is shared by zaps, nutzaps, onchain/bolt12 zaps, reactions and now git status events, so the zap-specific name was misleading. Moved it out of ZapEvent.kt into its own file; no behaviour change. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PicvdWRJ33zh4MCfjZj2kQ --- .../commons/ui/note/types/Bolt12ZapEvent.kt | 2 +- .../commons/ui/note/types/GitStatus.kt | 2 +- .../amethyst/commons/ui/note/types/Nutzap.kt | 2 +- .../commons/ui/note/types/OnchainZapEvent.kt | 2 +- .../commons/ui/note/types/Reaction.kt | 2 +- .../commons/ui/note/types/RenderTargetNote.kt | 58 +++++++++++++++++++ .../commons/ui/note/types/ZapEvent.kt | 30 +--------- 7 files changed, 64 insertions(+), 34 deletions(-) create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/RenderTargetNote.kt diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Bolt12ZapEvent.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Bolt12ZapEvent.kt index 13bf7db86b..ea4c1bf0e4 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Bolt12ZapEvent.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Bolt12ZapEvent.kt @@ -89,7 +89,7 @@ fun RenderBolt12Zap( }, ) - RenderZappedPost(note, quotesLeft, cardBackground, accountViewModel, nav) + RenderTargetNote(note, quotesLeft, cardBackground, accountViewModel, nav) amountSats?.let { ActivityAmountRow(PlatformNumberFormatter().format(it), orange) } diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/GitStatus.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/GitStatus.kt index d4245938c4..1ad0f90f4d 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/GitStatus.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/GitStatus.kt @@ -64,6 +64,6 @@ fun RenderGitStatusEvent( if (note.replyTo?.lastOrNull() != null) { Spacer(modifier = StdVertSpacer) - RenderZappedPost(note, quotesLeft, backgroundColor, accountViewModel, nav) + RenderTargetNote(note, quotesLeft, backgroundColor, accountViewModel, nav) } } diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Nutzap.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Nutzap.kt index 53e1b2bcb0..cea03f5759 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Nutzap.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Nutzap.kt @@ -93,7 +93,7 @@ fun RenderNutzap( }, ) - RenderZappedPost(note, quotesLeft, cardBackground, accountViewModel, nav) + RenderTargetNote(note, quotesLeft, cardBackground, accountViewModel, nav) ActivityAmountRow(showAmount(BigDecimal(nutzapEvent.claimedSatsTotal())), orange) diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/OnchainZapEvent.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/OnchainZapEvent.kt index 39c14e65c7..515b4bb5db 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/OnchainZapEvent.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/OnchainZapEvent.kt @@ -150,7 +150,7 @@ fun RenderOnchainZap( nav = nav, ) - RenderZappedPost(note, quotesLeft, cardBackground, accountViewModel, nav) + RenderTargetNote(note, quotesLeft, cardBackground, accountViewModel, nav) AmountRow(sats = sats, orange = orange) diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Reaction.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Reaction.kt index ec85bfb015..7c004995d0 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Reaction.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/Reaction.kt @@ -81,6 +81,6 @@ fun RenderReaction( }, ) - RenderZappedPost(note, quotesLeft, cardBackground, accountViewModel, nav) + RenderTargetNote(note, quotesLeft, cardBackground, accountViewModel, nav) } } diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/RenderTargetNote.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/RenderTargetNote.kt new file mode 100644 index 0000000000..6256da07b2 --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/RenderTargetNote.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.ui.note.types + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import com.vitorpamplona.amethyst.commons.model.Note +import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.note.NoteCompose +import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel + +/** + * Quotes the note that [note] acts on (the last entry of its `replyTo`) as a + * short embedded card: the post a zap or reaction targets, the issue/patch/PR + * a git status changes, and so on. + */ +@Composable +fun RenderTargetNote( + note: Note, + quotesLeft: Int, + backgroundColor: MutableState, + accountViewModel: AccountViewModel, + nav: INav, +) { + note.replyTo?.lastOrNull()?.let { + NoteCompose( + it, + modifier = Modifier, + isBoostedNote = true, + makeItShort = true, + unPackReply = ReplyRenderType.NONE, + quotesLeft = quotesLeft - 1, + parentBackgroundColor = backgroundColor, + accountViewModel = accountViewModel, + nav = nav, + ) + } +} diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/ZapEvent.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/ZapEvent.kt index 3d46b37ce2..91fbfaf5a7 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/ZapEvent.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/types/ZapEvent.kt @@ -52,7 +52,6 @@ import com.vitorpamplona.amethyst.commons.ui.note.ActivityCardFrame import com.vitorpamplona.amethyst.commons.ui.note.ActivityHeaderRow import com.vitorpamplona.amethyst.commons.ui.note.CrossfadeToDisplayComment import com.vitorpamplona.amethyst.commons.ui.note.DisplayBlankAuthor -import com.vitorpamplona.amethyst.commons.ui.note.NoteCompose import com.vitorpamplona.amethyst.commons.ui.note.UserPicture import com.vitorpamplona.amethyst.commons.ui.note.ZapIcon import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier @@ -70,33 +69,6 @@ import kotlinx.coroutines.IO import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext -/** - * Shows the post a zap targets above the transfer card, mirroring how - * reactions and reposts embed their target. - */ -@Composable -fun RenderZappedPost( - zapNote: Note, - quotesLeft: Int, - backgroundColor: MutableState, - accountViewModel: AccountViewModel, - nav: INav, -) { - zapNote.replyTo?.lastOrNull()?.let { - NoteCompose( - it, - modifier = Modifier, - isBoostedNote = true, - makeItShort = true, - unPackReply = ReplyRenderType.NONE, - quotesLeft = quotesLeft - 1, - parentBackgroundColor = backgroundColor, - accountViewModel = accountViewModel, - nav = nav, - ) - } -} - @Composable fun RenderZapReceipt( note: Note, @@ -155,7 +127,7 @@ fun RenderZapReceiptCard( }, ) - RenderZappedPost(note, quotesLeft, cardBackground, accountViewModel, nav) + RenderTargetNote(note, quotesLeft, cardBackground, accountViewModel, nav) card.amount?.let { ActivityAmountRow(it, orange) }