fix(concord): make the Messages-tab community chip reactive to the fold

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CzJ2Cwo8tg4oZq43oRa3ig
This commit is contained in:
Claude
2026-07-13 17:25:42 +00:00
parent fffeb78c2a
commit 824dee6fb6
2 changed files with 34 additions and 10 deletions
@@ -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) }
@@ -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<NormalizedRelayUrl>,
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. */