From f4688b57537aed8ff81ea485ca4b0f65a2ed0f38 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 29 Sep 2026 14:59:06 -0400 Subject: [PATCH] fix(concord): list joined communities in Messages before they fold Messages built Concord rows only from a folded Control Plane, so a community whose planes had not loaded (just joined, a genesis that never landed, or a relay that dropped it) had no row at all: no way to open it and no way to leave it, which read as the community being lost. Such a community now gets its community row in both view modes, titled with the name it was joined under until its metadata folds. Co-Authored-By: Claude Opus 5.5 --- .../chats/rooms/ChatroomHeaderCompose.kt | 16 +++++++++------- .../rooms/dal/ChatroomListKnownFeedFilter.kt | 16 ++++++++++++---- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt index bf4d44624d..0ce5c3e505 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt @@ -1036,15 +1036,17 @@ private fun ConcordServerRoomCompose( // Community name/icon from the folded Control Plane (bumped via the session revision). val revision by accountViewModel.account.concordSessions.revision .collectAsStateWithLifecycle() - val metadata = + val session = remember(row.communityId, revision) { - accountViewModel.account.concordSessions - .sessionFor(row.communityId) - ?.state - ?.value - ?.metadata + accountViewModel.account.concordSessions.sessionFor(row.communityId) } - val name = metadata?.name?.takeIf { it.isNotBlank() } ?: stringRes(Res.string.concord_home_title) + val metadata = remember(session, revision) { session?.state?.value?.metadata } + // Before the Control Plane folds there is no metadata; the name the community was joined under + // still tells the user which one it is. + val name = + metadata?.name?.takeIf { it.isNotBlank() } + ?: session?.entry?.name?.takeIf { it.isNotBlank() } + ?: stringRes(Res.string.concord_home_title) val author = row.newestMessage?.author val noteEvent = row.newestMessage?.event diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/chats/rooms/dal/ChatroomListKnownFeedFilter.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/chats/rooms/dal/ChatroomListKnownFeedFilter.kt index b0ddbebba3..0493032473 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/chats/rooms/dal/ChatroomListKnownFeedFilter.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/chats/rooms/dal/ChatroomListKnownFeedFilter.kt @@ -212,7 +212,11 @@ class ChatroomListKnownFeedFilter( when (account.settings.concordViewMode.value) { ConcordViewMode.INLINE -> account.concordSessions.sessions().flatMap { session -> - val state = session.state.value ?: return@flatMap emptyList() + // Not folded yet (just joined, or its Control Plane never arrived): no + // channels to list, so show the community itself. Skipping it made a + // community whose genesis is missing invisible everywhere, with no way in + // and no way to leave it. + val state = session.state.value ?: return@flatMap listOf(ConcordServerRoomNote(session.entry.id, null)) state.channels.keys.map { channelIdHex -> val channel = LocalCache.getOrCreateConcordChannel(ConcordChannelId(session.entry.id, channelIdHex)) channel.newestConcordNote(account) ?: channel.placeholderNote() @@ -221,10 +225,14 @@ class ChatroomListKnownFeedFilter( ConcordViewMode.GROUPED -> // One row per joined community, carrying the newest message across ALL its channels. - account.concordSessions.sessions().mapNotNull { session -> - val state = session.state.value ?: return@mapNotNull null + account.concordSessions.sessions().map { session -> + // An unfolded community still gets its row (see INLINE above). + val state = session.state.value val newest = - state.channels.keys + state + ?.channels + ?.keys + .orEmpty() .mapNotNull { LocalCache.getOrCreateConcordChannel(ConcordChannelId(session.entry.id, it)).newestConcordNote(account) } .maxByOrNull { it.createdAt() ?: 0L } ConcordServerRoomNote(session.entry.id, newest)