fix(concord): show the quoted parent on kind-9 chat replies in NoteCompose

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) <noreply@anthropic.com>
This commit is contained in:
Vitor Pamplona
2026-07-14 21:58:48 -04:00
co-authored by Claude Opus 4.8
parent 4ec44ad241
commit 08c1d1d539
3 changed files with 28 additions and 0 deletions
@@ -1455,6 +1455,7 @@ private fun RenderNoteRow(
makeItShort,
canPreview,
quotesLeft,
unPackReply,
backgroundColor,
accountViewModel,
nav,
@@ -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<Color>,
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(
@@ -1042,6 +1042,7 @@ private fun FullBleedNoteCompose(
makeItShort = false,
canPreview = canPreview,
quotesLeft = 3,
unPackReply = ReplyRenderType.NONE,
backgroundColor = backgroundColor,
accountViewModel = accountViewModel,
nav = nav,