mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-11 08:47:33 +00:00
feat(chat): delivery ticks for own Concord channel messages
The Concord send path published the encrypted plane wrap but never registered with chatDeliveryTracker, so own messages showed no acceptance ticks. Relays OK the wrap (kind-1059) while the feed row is the decrypted inner rumor, so a plain trackPublic wouldn't match. Added trackWrappedPublic, which maps the wrap id onto the displayed rumor id (relay-only delivery, no per-recipient breakdown — the same ticks a public room gets). The Concord message/image send paths re-open the wrap they just built to key the tracker by the rumor id; reactions and typing wraps are skipped since they never become a feed row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0129yvP2hmVeDFfuKKy94tqX
This commit is contained in:
@@ -158,6 +158,8 @@ import com.vitorpamplona.quartz.concord.cord04Roles.MetadataEntity
|
||||
import com.vitorpamplona.quartz.concord.cord04Roles.RoleEntity
|
||||
import com.vitorpamplona.quartz.concord.cord05Invites.CommunityInvite
|
||||
import com.vitorpamplona.quartz.concord.cord05Invites.InviteRelayDictionary
|
||||
import com.vitorpamplona.quartz.concord.crypto.GroupKey
|
||||
import com.vitorpamplona.quartz.concord.envelope.ConcordStreamEnvelope
|
||||
import com.vitorpamplona.quartz.experimental.bounties.BountyAddValueEvent
|
||||
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryBaseEvent
|
||||
@@ -2099,6 +2101,7 @@ class Account(
|
||||
else ->
|
||||
ConcordActions.buildChannelMessage(signer, channelKey, channelIdHex, entry.rootEpoch, text, TimeUtils.now(), emojiTags)
|
||||
}
|
||||
trackConcordDelivery(entry, channelKey, wrap)
|
||||
publishConcordWrap(entry, wrap)
|
||||
return true
|
||||
}
|
||||
@@ -2123,6 +2126,7 @@ class Account(
|
||||
// Carry NIP-30 custom-emoji tags for any `:shortcode:` in the caption, same as a plain message.
|
||||
val emojiTags = emoji.findEmojiTags(text).map { it.toTagArray() }.toTypedArray()
|
||||
val wrap = ConcordActions.buildChannelImageMessage(signer, channelKey, channelIdHex, entry.rootEpoch, text, imetas, TimeUtils.now(), emojiTags)
|
||||
trackConcordDelivery(entry, channelKey, wrap)
|
||||
publishConcordWrap(entry, wrap)
|
||||
return true
|
||||
}
|
||||
@@ -2233,6 +2237,23 @@ class Account(
|
||||
if (relays.isNotEmpty()) client.publish(wrap, relays)
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an own Concord channel message with the delivery tracker so its chat
|
||||
* bubble shows relay-acceptance ticks. Relays OK the encrypted [wrap], but the feed
|
||||
* shows the inner rumor, so we re-open the wrap (we just built it, so this always
|
||||
* succeeds) to key the tracker by the rumor id the bubble is drawn from. Reactions
|
||||
* and typing wraps skip this — they never become a feed row.
|
||||
*/
|
||||
private fun trackConcordDelivery(
|
||||
entry: ConcordCommunityListEntry,
|
||||
channelKey: GroupKey,
|
||||
wrap: Event,
|
||||
) {
|
||||
val rumorId = ConcordStreamEnvelope.openOrNull(wrap, channelKey)?.rumor?.id ?: return
|
||||
val relays = entry.relays.mapNotNullTo(mutableSetOf()) { RelayUrlNormalizer.normalizeOrNull(it) }
|
||||
chatDeliveryTracker.trackWrappedPublic(rumorId, wrap.id, relays)
|
||||
}
|
||||
|
||||
// ── Concord roles & moderation (CORD-04) ─────────────────────────────────
|
||||
// Each publishes a Control Plane edition; authority is enforced at fold time by
|
||||
// every client's AuthorityResolver, so a call by someone who doesn't outrank the
|
||||
|
||||
+22
@@ -149,6 +149,28 @@ class ChatDeliveryTracker(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a group message that was broadcast as a single encrypted [wrapId] to
|
||||
* the group's [targetRelays] (Concord channels: the whole channel shares one
|
||||
* plane wrap, not a per-recipient gift wrap). Relays OK the wrap, but the feed
|
||||
* shows the decrypted inner rumor, so [displayedNoteId] (the rumor id) is mapped
|
||||
* through [wrapId] — like [trackPublic] (relay ticks, no per-recipient breakdown)
|
||||
* but keyed by the wrap the relay actually acknowledges.
|
||||
*/
|
||||
fun trackWrappedPublic(
|
||||
displayedNoteId: HexKey,
|
||||
wrapId: HexKey,
|
||||
targetRelays: Set<NormalizedRelayUrl>,
|
||||
) {
|
||||
if (targetRelays.isEmpty()) return
|
||||
synchronized(lock) {
|
||||
flowForLocked(displayedNoteId).value = ChatDelivery(targetRelays)
|
||||
// recipient is unused for a relay-only delivery (recipients stays null, so
|
||||
// onAccepted never matches on it); reuse the note id as a harmless value.
|
||||
wrapIndex = wrapIndex + (wrapId to (displayedNoteId to displayedNoteId))
|
||||
}
|
||||
}
|
||||
|
||||
fun deliveryFlow(noteId: HexKey): StateFlow<ChatDelivery?> =
|
||||
synchronized(lock) {
|
||||
flowForLocked(noteId)
|
||||
|
||||
Reference in New Issue
Block a user