From 824dee6fb691cb36cefcaaee43ea941182306da2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 13 Jul 2026 17:25:42 +0000 Subject: [PATCH] fix(concord): make the Messages-tab community chip reactive to the fold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chip that names each Concord channel's parent community (and opens it on tap) renders only when channel.communityName is set. That value is populated by refreshConcordChannelIndex -> ConcordChannel.updateFrom on each Control Plane fold, but nothing invalidated the channel's metadata flow afterward, so the row (which observes metadata.stateFlow via observeChannel) never recomposed to show the chip — it appeared only if the row happened to recompose for another reason. updateFrom now returns whether a displayed field actually changed, and the index refresh calls updateChannelInfo() only on a real change, so the community name, icon and chip recompose the moment the fold resolves them, without churning every row on every fold tick. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01CzJ2Cwo8tg4oZq43oRa3ig --- .../vitorpamplona/amethyst/model/Account.kt | 5 ++- .../commons/model/concord/ConcordChannel.kt | 39 ++++++++++++++----- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index e670738245..e56d1f6f03 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -516,7 +516,10 @@ class Account( val relays = relaysByCommunity[communityId] ?: emptySet() for (channelIdHex in state.channels.keys) { val channel = cache.getOrCreateConcordChannel(ConcordChannelId(communityId, channelIdHex)) - channel.updateFrom(state, relays, myPubKey) + // Invalidate the channel's metadata flow only on a real change so the Messages-row + // name + community chip recompose when the fold first resolves them (they observe + // metadata.stateFlow via observeChannel), without churning every row every tick. + if (channel.updateFrom(state, relays, myPubKey)) channel.updateChannelInfo() channel.notes .filter { _, note -> note.event?.pubKey?.let { state.authority.isBanned(it) } == true } .forEach { channel.removeNote(it) } diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/concord/ConcordChannel.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/concord/ConcordChannel.kt index 5854f1fc0c..acb8fe9c77 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/concord/ConcordChannel.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/concord/ConcordChannel.kt @@ -76,21 +76,42 @@ class ConcordChannel( * Refresh this channel's metadata from a freshly-folded community [state] plus * the community's [relays] and this account's [myPubKey]. Cheap and idempotent * — called whenever the Control Plane re-folds. + * + * Returns true when a displayed field (channel name, community name/icon, + * membership) actually changed, so the caller can invalidate the channel's + * metadata flow ([updateChannelInfo]) — and thus recompose the Messages-row + * name + community chip — only on a real change, not on every fold tick. */ fun updateFrom( state: ConcordCommunityState, relays: Set, myPubKey: HexKey, - ) { - state.channels[channelId.channelId]?.definition?.let { - channelName = it.name - isVoice = it.voice - isPrivate = it.private - } - communityName = state.metadata?.name - communityIcon = state.metadata?.icon + ): Boolean { + val def = state.channels[channelId.channelId]?.definition + // Channel fields keep their prior value until the channel edition folds. + val newChannelName = def?.name ?: channelName + val newVoice = def?.voice ?: isVoice + val newPrivate = def?.private ?: isPrivate + val newCommunityName = state.metadata?.name + val newCommunityIcon = state.metadata?.icon + val newMembership = ConcordMembership.of(state.authority, myPubKey) + + val changed = + channelName != newChannelName || + isVoice != newVoice || + isPrivate != newPrivate || + communityName != newCommunityName || + communityIcon != newCommunityIcon || + membership != newMembership + + channelName = newChannelName + isVoice = newVoice + isPrivate = newPrivate + communityName = newCommunityName + communityIcon = newCommunityIcon communityRelays = relays - membership = ConcordMembership.of(state.authority, myPubKey) + membership = newMembership + return changed } /** A Concord channel is reachable on any of its community's relays. */