diff --git a/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/ConcordChannelCommands.kt b/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/ConcordChannelCommands.kt
index 1e0fedccc2..a99513d2d1 100644
--- a/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/ConcordChannelCommands.kt
+++ b/cli/src/main/kotlin/com/vitorpamplona/amethyst/cli/commands/ConcordChannelCommands.kt
@@ -92,6 +92,10 @@ object ConcordChannelCommands {
if (state.dissolved) {
return Output.error("dissolved", "community '$handle' has been dissolved and is read-only (CORD-02 §9)")
}
+ // CORD-04 §4: every reader drops a banned author's messages, so a post would vanish unseen.
+ if (state.authority.isBanned(ctx.signer.pubKey)) {
+ return Output.error("banned", "this account is banned from '$handle' (CORD-04 §4); its messages are hidden from everyone")
+ }
val channelId = resolve(ctx, sc, channelRef) ?: return Output.error("not_found", "no channel '$channelRef'")
// The channel's own plane (CORD-03 §1): root-derived when Public, its held key when
// Private — and a refusal, never the root plane, for a Private Channel we hold no key for.
diff --git a/commonsUI/src/commonMain/composeResources/values/strings.xml b/commonsUI/src/commonMain/composeResources/values/strings.xml
index db3cfefd5e..8c402b5bff 100644
--- a/commonsUI/src/commonMain/composeResources/values/strings.xml
+++ b/commonsUI/src/commonMain/composeResources/values/strings.xml
@@ -3713,6 +3713,7 @@
Ban
Ban member?
They can no longer post, and their messages are hidden from everyone. Other apps may remove the community from their list. They can still read new messages until you also remove them from the community. You can unban them later.
+ They can no longer post, and their messages are hidden from everyone. This community is Private, so banning also rotates its keys: they lose access to new messages. You can unban them later, but they will need a new invite to read again.
Make admin
Remove admin
Could not update this member's roles.
diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMembersScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMembersScreen.kt
index 27b70f9428..d29dfcf49f 100644
--- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMembersScreen.kt
+++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMembersScreen.kt
@@ -64,6 +64,7 @@ import com.vitorpamplona.amethyst.commons.resources.back
import com.vitorpamplona.amethyst.commons.resources.cancel
import com.vitorpamplona.amethyst.commons.resources.concord_members_ban
import com.vitorpamplona.amethyst.commons.resources.concord_members_ban_message
+import com.vitorpamplona.amethyst.commons.resources.concord_members_ban_message_refound
import com.vitorpamplona.amethyst.commons.resources.concord_members_ban_title
import com.vitorpamplona.amethyst.commons.resources.concord_members_empty
import com.vitorpamplona.amethyst.commons.resources.concord_members_kick
@@ -238,6 +239,9 @@ fun ConcordMembersScreen(
state?.authority?.canActOn(myPubKey, entry.pubkey, ConcordPermissions.BAN) == true,
// A Kick needs KICK and a strict outrank of the target (CORD-04 §6), the same rank rule.
canKickTarget = iCanKick && state?.authority?.canActOn(myPubKey, entry.pubkey, ConcordPermissions.KICK) == true,
+ // In a Private community a ban also Refounds (CORD-05 §5), so the dialog must not
+ // promise they keep reading.
+ banRotatesKeys = state?.banRequiresRefounding(listOf(entry.pubkey)) == true,
viewerCanManageRoles = iCanManageRoles,
// canActOn folds the whole rank rule for us: we hold MANAGE_ROLES, we're not
// banned, the target isn't the owner (unremovable), and we strictly outrank
@@ -263,6 +267,7 @@ private fun ConcordMemberRow(
viewerCanBan: Boolean,
canBanTarget: Boolean,
canKickTarget: Boolean,
+ banRotatesKeys: Boolean,
viewerCanManageRoles: Boolean,
canManageRolesOnTarget: Boolean,
assignableRoles: List,
@@ -326,7 +331,7 @@ private fun ConcordMemberRow(
if (confirmBan) {
ConcordConfirmMemberActionDialog(
title = Res.string.concord_members_ban_title,
- message = Res.string.concord_members_ban_message,
+ message = if (banRotatesKeys) Res.string.concord_members_ban_message_refound else Res.string.concord_members_ban_message,
confirm = Res.string.concord_members_ban,
onConfirm = {
accountViewModel.setConcordBan(communityId, entry.pubkey, ban = true)