mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 08:04:45 +00:00
feat(buzz): widen group REQ to richer kinds + kind-aware renderers
Read-path interop. Two changes: 1. RelayGroupFilterBuilders: BUZZ_RELAY_GROUP_TIMELINE_EXTRA_KINDS now also requests canvas (40100), forum posts/votes/comments (45001-45003), agent jobs (43001-43006), and huddle lifecycle (48100-48103) — all h-scoped, so the same #h group REQ returns them. Previously only 40002/40003/40008/40099 were requested, so LocalCache could consume these kinds but they never arrived for a group feed. 2. RenderBuzzNotes: kind-aware rows dispatched from ChatMessageCompose — RenderBuzzDiff (40008: monospace, horizontally-scrollable diff with a repo/commit/file header instead of prose-wrapped gutters), RenderBuzzActivityRow (job + huddle lifecycle as centered system narration — huddles MUST be caught here since their content is JSON), and RenderBuzzForumVote (45002). Forum posts stay as plain chat bubbles (their content is already plain text). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8KBSw6smQRyXLiWHeDsZ8
This commit is contained in:
+16
@@ -56,7 +56,10 @@ import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.DisplayZapSplits
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.layouts.ChatBubbleLayout
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.layouts.ChatGroupPosition
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.RenderBuzzActivityRow
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.RenderBuzzDiff
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.RenderBuzzEditedNote
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.RenderBuzzForumVote
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.RenderBuzzSystemMessage
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.RenderChannelAdminSystemMessage
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.RenderChatClip
|
||||
@@ -67,9 +70,12 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.RenderEncr
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.RenderMarmotEncryptedMedia
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.RenderRegularTextNote
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.hasMip04Media
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.isBuzzActivityRow
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.observeBuzzEdit
|
||||
import com.vitorpamplona.amethyst.ui.theme.ReactionRowZapraiser
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.quartz.buzz.forum.ForumVoteEvent
|
||||
import com.vitorpamplona.quartz.buzz.stream.StreamMessageDiffEvent
|
||||
import com.vitorpamplona.quartz.buzz.stream.SystemMessageEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
@@ -136,6 +142,16 @@ fun ChatroomMessageCompose(
|
||||
} else if (event is SystemMessageEvent) {
|
||||
// Buzz kind-40099: relay-signed room narration (join/leave/topic).
|
||||
RenderBuzzSystemMessage(baseNote)
|
||||
} else if (event is StreamMessageDiffEvent) {
|
||||
// Buzz kind-40008: a code/text diff pushed into the channel.
|
||||
RenderBuzzDiff(baseNote)
|
||||
} else if (event is ForumVoteEvent) {
|
||||
// Buzz kind-45002: a forum up/down vote.
|
||||
RenderBuzzForumVote(baseNote)
|
||||
} else if (isBuzzActivityRow(event)) {
|
||||
// Buzz agent-job (43xxx) and huddle (48xxx) lifecycle narration. Huddles
|
||||
// especially must be caught here — their content is JSON, not chat text.
|
||||
RenderBuzzActivityRow(baseNote)
|
||||
} else {
|
||||
NormalChatNote(
|
||||
baseNote,
|
||||
|
||||
+135
@@ -20,7 +20,12 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types
|
||||
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -30,6 +35,9 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||
@@ -41,7 +49,20 @@ import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.layouts.ChatSystemMessage
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.buzz.forum.ForumVoteEvent
|
||||
import com.vitorpamplona.quartz.buzz.huddles.HuddleEndedEvent
|
||||
import com.vitorpamplona.quartz.buzz.huddles.HuddleParticipantJoinedEvent
|
||||
import com.vitorpamplona.quartz.buzz.huddles.HuddleParticipantLeftEvent
|
||||
import com.vitorpamplona.quartz.buzz.huddles.HuddleStartedEvent
|
||||
import com.vitorpamplona.quartz.buzz.jobs.JobAcceptedEvent
|
||||
import com.vitorpamplona.quartz.buzz.jobs.JobCancelEvent
|
||||
import com.vitorpamplona.quartz.buzz.jobs.JobErrorEvent
|
||||
import com.vitorpamplona.quartz.buzz.jobs.JobProgressEvent
|
||||
import com.vitorpamplona.quartz.buzz.jobs.JobRequestEvent
|
||||
import com.vitorpamplona.quartz.buzz.jobs.JobResultEvent
|
||||
import com.vitorpamplona.quartz.buzz.stream.StreamMessageDiffEvent
|
||||
import com.vitorpamplona.quartz.buzz.stream.SystemMessageEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip29RelayGroups.groupId
|
||||
|
||||
/**
|
||||
@@ -131,3 +152,117 @@ fun RenderBuzzSystemMessage(note: Note) {
|
||||
|
||||
ChatSystemMessage(text = text)
|
||||
}
|
||||
|
||||
/**
|
||||
* True for the Buzz agent-job and huddle lifecycle kinds that [RenderBuzzActivityRow]
|
||||
* narrates as a centered system line rather than a chat bubble. Huddle events in
|
||||
* particular MUST be caught here — their `content` is JSON, so rendering them as a plain
|
||||
* chat message would show raw `{"ephemeral_channel_id":…}`.
|
||||
*/
|
||||
fun isBuzzActivityRow(event: Event?): Boolean =
|
||||
event is JobRequestEvent ||
|
||||
event is JobAcceptedEvent ||
|
||||
event is JobProgressEvent ||
|
||||
event is JobResultEvent ||
|
||||
event is JobCancelEvent ||
|
||||
event is JobErrorEvent ||
|
||||
event is HuddleStartedEvent ||
|
||||
event is HuddleParticipantJoinedEvent ||
|
||||
event is HuddleParticipantLeftEvent ||
|
||||
event is HuddleEndedEvent
|
||||
|
||||
/**
|
||||
* Narrates a Buzz agent-job or huddle lifecycle event as a centered system line. The
|
||||
* label is derived from the kind; job progress/result/error also append a short content
|
||||
* snippet (the human-readable status/result/error the agent wrote).
|
||||
*/
|
||||
@Composable
|
||||
fun RenderBuzzActivityRow(note: Note) {
|
||||
val event = note.event ?: return
|
||||
val text =
|
||||
remember(event) {
|
||||
when (event) {
|
||||
is JobRequestEvent -> "⚙ job requested" + event.request().snippet()
|
||||
is JobAcceptedEvent -> "⚙ job accepted"
|
||||
is JobProgressEvent -> "⚙ job progress" + (event.status()?.let { ": $it" } ?: "") + event.content.snippet()
|
||||
is JobResultEvent -> "⚙ job result" + event.result().snippet()
|
||||
is JobCancelEvent -> "⚙ job cancelled"
|
||||
is JobErrorEvent -> "⚠ job error" + event.error().snippet()
|
||||
is HuddleStartedEvent -> "🔊 huddle started"
|
||||
is HuddleParticipantJoinedEvent -> "🔊 someone joined the huddle"
|
||||
is HuddleParticipantLeftEvent -> "🔊 someone left the huddle"
|
||||
is HuddleEndedEvent -> "🔊 huddle ended"
|
||||
else -> event.content.take(120)
|
||||
}
|
||||
}
|
||||
ChatSystemMessage(text = text)
|
||||
}
|
||||
|
||||
/** A short one-line snippet of free-text content appended after a label, or "" if blank. */
|
||||
private fun String.snippet(): String {
|
||||
val oneLine = trim().replace('\n', ' ')
|
||||
if (oneLine.isEmpty()) return ""
|
||||
return ": " + if (oneLine.length > 80) oneLine.take(80) + "…" else oneLine
|
||||
}
|
||||
|
||||
/**
|
||||
* A Buzz kind-40008 stream diff: a code/text diff pushed into the channel. Renders the
|
||||
* repo/commit/file header from the `DiffMeta` tags plus the diff body in a monospace,
|
||||
* horizontally-scrollable block, rather than the plain-text fallback (which mangles the
|
||||
* `+`/`-` gutters into wrapped prose).
|
||||
*/
|
||||
@Composable
|
||||
fun RenderBuzzDiff(note: Note) {
|
||||
val event = note.event as? StreamMessageDiffEvent ?: return
|
||||
val meta = remember(event) { event.diffMeta() }
|
||||
|
||||
Card(modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)) {
|
||||
Column(modifier = Modifier.padding(12.dp)) {
|
||||
val header =
|
||||
remember(meta) {
|
||||
meta?.let {
|
||||
buildString {
|
||||
it.filePath?.let(::append)
|
||||
it.language?.let { lang -> append(if (isEmpty()) lang else " · $lang") }
|
||||
it.prNumber?.let { pr -> append(" · PR #$pr") }
|
||||
it.commitSha
|
||||
?.takeIf { sha -> sha.isNotBlank() }
|
||||
?.let { sha -> append(" · ${sha.take(8)}") }
|
||||
}.ifBlank { null }
|
||||
}
|
||||
}
|
||||
if (header != null) {
|
||||
Text(
|
||||
text = header,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = event.content,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()),
|
||||
)
|
||||
if (meta?.truncated == true) {
|
||||
Text(
|
||||
text = stringRes(R.string.buzz_diff_truncated),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
fontSize = 10.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A Buzz kind-45002 forum vote: a lightweight up/down signal, shown as a system line. */
|
||||
@Composable
|
||||
fun RenderBuzzForumVote(note: Note) {
|
||||
val event = note.event as? ForumVoteEvent ?: return
|
||||
// Content is the vote token ("+"/"-" or similar); show a compact glyph line.
|
||||
val text = remember(event) { if (event.content.trim().startsWith("-")) "▼ downvoted a post" else "▲ upvoted a post" }
|
||||
ChatSystemMessage(text = text)
|
||||
}
|
||||
|
||||
+39
-2
@@ -20,6 +20,20 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource
|
||||
|
||||
import com.vitorpamplona.quartz.buzz.forum.ForumCommentEvent
|
||||
import com.vitorpamplona.quartz.buzz.forum.ForumPostEvent
|
||||
import com.vitorpamplona.quartz.buzz.forum.ForumVoteEvent
|
||||
import com.vitorpamplona.quartz.buzz.huddles.HuddleEndedEvent
|
||||
import com.vitorpamplona.quartz.buzz.huddles.HuddleParticipantJoinedEvent
|
||||
import com.vitorpamplona.quartz.buzz.huddles.HuddleParticipantLeftEvent
|
||||
import com.vitorpamplona.quartz.buzz.huddles.HuddleStartedEvent
|
||||
import com.vitorpamplona.quartz.buzz.jobs.JobAcceptedEvent
|
||||
import com.vitorpamplona.quartz.buzz.jobs.JobCancelEvent
|
||||
import com.vitorpamplona.quartz.buzz.jobs.JobErrorEvent
|
||||
import com.vitorpamplona.quartz.buzz.jobs.JobProgressEvent
|
||||
import com.vitorpamplona.quartz.buzz.jobs.JobRequestEvent
|
||||
import com.vitorpamplona.quartz.buzz.jobs.JobResultEvent
|
||||
import com.vitorpamplona.quartz.buzz.stream.CanvasEvent
|
||||
import com.vitorpamplona.quartz.buzz.stream.StreamMessageDiffEvent
|
||||
import com.vitorpamplona.quartz.buzz.stream.StreamMessageEditEvent
|
||||
import com.vitorpamplona.quartz.buzz.stream.StreamMessageV2Event
|
||||
@@ -91,12 +105,21 @@ val RELAY_GROUP_TIMELINE_KINDS = listOf(ChatEvent.KIND, PollEvent.KIND)
|
||||
|
||||
/**
|
||||
* Extra timeline kinds a `block/buzz` workspace relay serves in the same `h`-scoped
|
||||
* channels: stream messages v2 (40002), edits (40003), diffs (40008) and system rows
|
||||
* (40099). Requested UNCONDITIONALLY alongside the NIP-29 set — on a vanilla relay the
|
||||
* channels, requested UNCONDITIONALLY alongside the NIP-29 set — on a vanilla relay the
|
||||
* kinds simply match nothing. A dialect-gated version was tried and reverted: gating
|
||||
* creates a bootstrap hole (nothing asks for a Buzz kind until one is consumed) and a
|
||||
* worse one — history pages fetched before the mark advance their cursors past ranges
|
||||
* queried WITHOUT Buzz kinds, permanently skipping older workspace messages.
|
||||
*
|
||||
* All are `h`-scoped (`GroupIdTag`), so the same `#h` group REQ returns them:
|
||||
* - stream messages v2 (40002), edits (40003), diffs (40008), system rows (40099), canvas (40100)
|
||||
* - forum posts/votes/comments (45001-45003)
|
||||
* - agent jobs (43001-43006)
|
||||
* - huddle lifecycle (48100-48103)
|
||||
*
|
||||
* Consumption for every one of these already exists in `LocalCache` (see
|
||||
* `consumeBuzzTimelineEvent`); requesting them here is what lets them actually arrive for
|
||||
* a group feed instead of only appearing if another subscription happened to fetch them.
|
||||
*/
|
||||
val BUZZ_RELAY_GROUP_TIMELINE_EXTRA_KINDS =
|
||||
listOf(
|
||||
@@ -104,6 +127,20 @@ val BUZZ_RELAY_GROUP_TIMELINE_EXTRA_KINDS =
|
||||
StreamMessageEditEvent.KIND,
|
||||
StreamMessageDiffEvent.KIND,
|
||||
SystemMessageEvent.KIND,
|
||||
CanvasEvent.KIND,
|
||||
ForumPostEvent.KIND,
|
||||
ForumVoteEvent.KIND,
|
||||
ForumCommentEvent.KIND,
|
||||
JobRequestEvent.KIND,
|
||||
JobAcceptedEvent.KIND,
|
||||
JobProgressEvent.KIND,
|
||||
JobResultEvent.KIND,
|
||||
JobCancelEvent.KIND,
|
||||
JobErrorEvent.KIND,
|
||||
HuddleStartedEvent.KIND,
|
||||
HuddleParticipantJoinedEvent.KIND,
|
||||
HuddleParticipantLeftEvent.KIND,
|
||||
HuddleEndedEvent.KIND,
|
||||
)
|
||||
|
||||
/** The timeline kinds requested for every relay-group REQ (NIP-29 + Buzz; see above). */
|
||||
|
||||
@@ -3291,6 +3291,7 @@
|
||||
<string name="chat_system_created_channel_unnamed">%1$s created the channel</string>
|
||||
<string name="chat_system_updated_channel">%1$s updated the channel profile</string>
|
||||
<string name="buzz_message_edited">(edited)</string>
|
||||
<string name="buzz_diff_truncated">(diff truncated)</string>
|
||||
|
||||
<string name="chat_delivery_details_title">Message Delivery</string>
|
||||
<string name="close">Close</string>
|
||||
|
||||
Reference in New Issue
Block a user