From 08c1d1d5395b520bd17e94f63c6caeb85c2fa041 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 14 Jul 2026 20:56:00 -0400 Subject: [PATCH] fix(concord): show the quoted parent on kind-9 chat replies in NoteCompose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Concord (and MLS/WhiteNoise) chat reply is a kind-9 ChatEvent that carries its reply target as a NIP-18 `q` (or NIP-10 `e`) tag, not a NIP-10 thread — so it isn't a BaseThreadedEvent and RenderTextEvent's reply-to preview never fires for it. On the chat feed the preview is drawn by chat-only code, but everywhere else NoteCompose routes kind-9 through RenderChat, which rendered only the content and never `note.replyTo`. Result: on the Notifications tab a Concord reply showed no quoted parent (no border) — most visibly when replying to an image, whose target is likewise a kind-9. RenderChat now takes unPackReply and, when FULL and not makeItShort, renders ReplyNoteComposition(note.replyTo.lastOrNull()) like the threaded path does, skipping it when the parent is already cited inline (`nostr:...`) so an MLS-style quote isn't drawn twice. NoteCompose forwards unPackReply; the thread view passes NONE since its structure already shows the parent. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../amethyst/ui/note/NoteCompose.kt | 1 + .../amethyst/ui/note/types/Chat.kt | 26 +++++++++++++++++++ .../loggedIn/threadview/ThreadFeedView.kt | 1 + 3 files changed, 28 insertions(+) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 940f01cd13..5ae73506f1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -1455,6 +1455,7 @@ private fun RenderNoteRow( makeItShort, canPreview, quotesLeft, + unPackReply, backgroundColor, accountViewModel, nav, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Chat.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Chat.kt index e51612e3a7..53c00eaa9a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Chat.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Chat.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.note.types +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -35,10 +36,13 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.ui.note.ReplyNoteComposition import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags +import com.vitorpamplona.quartz.nip10Notes.BaseNoteEvent @Composable fun RenderChat( @@ -46,6 +50,7 @@ fun RenderChat( makeItShort: Boolean, canPreview: Boolean, quotesLeft: Int, + unPackReply: ReplyRenderType, backgroundColor: MutableState, accountViewModel: AccountViewModel, nav: INav, @@ -65,6 +70,27 @@ fun RenderChat( overflow = TextOverflow.Ellipsis, ) } else { + // A kind-9 chat message carries its reply target as a NIP-18 `q` (or NIP-10 `e`) + // tag, NOT as a NIP-10 thread — so it's not a BaseThreadedEvent and RenderTextEvent's + // reply-to preview never fires for it. Render the quoted parent here so a Concord/MLS + // chat reply shows what it's replying to wherever NoteCompose draws it (Notifications + // tab, feed, threads) — the chat feed has its own reply-row and passes NONE. + if (unPackReply == ReplyRenderType.FULL && !makeItShort) { + val replyingDirectlyTo = + remember(note) { + // Skip the preview when the parent is already cited inline (`nostr:...`) in the + // message — quotesLeft renders it at that spot, so a top preview would duplicate + // it. Happens with MLS/WhiteNoise quotes; Concord `q` replies aren't cited inline. + note.replyTo?.lastOrNull()?.takeUnless { parent -> + (noteEvent as? BaseNoteEvent)?.findCitations()?.contains(parent.idHex) == true + } + } + if (replyingDirectlyTo != null) { + ReplyNoteComposition(replyingDirectlyTo, backgroundColor, accountViewModel, nav) + Spacer(modifier = StdVertSpacer) + } + } + val callbackUri = remember(note) { note.toNostrUri() } SensitivityWarning( 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 51993a25a9..e0e96e33f3 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 @@ -1042,6 +1042,7 @@ private fun FullBleedNoteCompose( makeItShort = false, canPreview = canPreview, quotesLeft = 3, + unPackReply = ReplyRenderType.NONE, backgroundColor = backgroundColor, accountViewModel = accountViewModel, nav = nav,