From 5c8060b69002824e99d61400931224834bcde572 Mon Sep 17 00:00:00 2001 From: Paul Hitt Date: Wed, 26 Aug 2026 07:30:19 -0400 Subject: [PATCH] Groups verified, and stop calling the user a number MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Groups now driven end to end on Android against relay.flamenet.io: created "Testers" with a member, it appears in the list, it SURVIVES a full `pm clear` and re-sign-in (restored from the relay), it opens, and a message sends into it. This was one of the original asks and had never been exercised. Two things it turned up: 🔴 **The relay's 401 also blamed the passphrase.** The AccountsClient fix in the previous commit was correct and incomplete: `describe()` mapped `RelayException.Unauthorized` to "That handle or passphrase isn't right" too, and that is the path that actually fires when a relay token ages out. Same lie, same consequence — a user changing a password that was never wrong. It now says the session expired, which is what happened. ⚠️ **You are never in your own buddy list**, so every "find the name among the buddies" fallback rendered the signed-in user as `user 31`. The group row read "user 31, fnreviewbeta". `Phase.SignedIn` now carries its own `userId` and both fallbacks say "You". Verified live: the row reads "You, fnreviewbeta". Co-Authored-By: Claude Opus 5 --- .../io/flamenet/messenger/app/ChatScreens.kt | 6 ++++- .../messenger/app/MessengerViewModel.kt | 22 +++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt b/app/src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt index 02d0984..e9eb87f 100644 --- a/app/src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt +++ b/app/src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt @@ -364,8 +364,12 @@ fun ColumnScope.GroupsSection( fontWeight = if (thread in phase.unread) FontWeight.Bold else null, ) Text( + // "You" first: you are a member of your own group and are + // not in your own buddy list, so this used to read + // "user 31, fnreviewbeta". group.memberIds.joinToString(", ") { id -> - phase.buddies.firstOrNull { it.userId == id } + if (id == phase.userId) "You" + else phase.buddies.firstOrNull { it.userId == id } ?.let { b -> b.displayName.ifBlank { b.handle } } ?: "user $id" }, diff --git a/app/src/main/kotlin/io/flamenet/messenger/app/MessengerViewModel.kt b/app/src/main/kotlin/io/flamenet/messenger/app/MessengerViewModel.kt index 25278b8..b7c640e 100644 --- a/app/src/main/kotlin/io/flamenet/messenger/app/MessengerViewModel.kt +++ b/app/src/main/kotlin/io/flamenet/messenger/app/MessengerViewModel.kt @@ -38,6 +38,15 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) { data class SignedIn( val handle: String, + /** + * This account's own id. + * + * ⚠️ Needed because **you are never in your own buddy list**, so + * every "look the name up among the buddies" fallback rendered you + * as `user 31` — visible on the group row, which listed its members + * as "user 31, fnreviewbeta". + */ + val userId: Long, val deviceId: String, val relay: String, val status: String, @@ -214,6 +223,7 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) { ) stored to Phase.SignedIn( handle = credential.username, + userId = credential.userId, deviceId = stored.deviceId, relay = SiteConfig(credential.relayBaseUrl).host, status = "Signed in on this device.", @@ -326,6 +336,7 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) { return Phase.SignedIn( handle = account.username, + userId = account.userId, deviceId = device.deviceId, relay = config.host, status = "Registered with ${preKeys.size} one-time prekeys published.", @@ -776,7 +787,8 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) { getApplication().getSharedPreferences("calls", android.content.Context.MODE_PRIVATE) private fun nameFor(phase: Phase.SignedIn, userId: Long): String = - phase.buddies.firstOrNull { it.userId == userId } + if (userId == phase.userId) "You" + else phase.buddies.firstOrNull { it.userId == userId } ?.let { it.displayName.ifBlank { it.handle } } ?: "user $userId" /** @@ -1171,7 +1183,13 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) { is AccountsClient.Failure.HandleTaken -> "That handle is already taken." is AccountsClient.Failure.RateLimited -> "Too many attempts. Wait a few minutes." is AccountsClient.Failure.Transport -> failure.message.orEmpty() - is RelayException.Unauthorized -> "That handle or passphrase isn't right." + // ⚠️ NOT "that passphrase is wrong". The relay rejects a TOKEN, which + // the user never typed and cannot check. Telling them their passphrase + // is wrong when their session merely aged out sends them off to change + // a password that was fine — observed on a send ten hours after a + // successful sign-in. + is RelayException.Unauthorized -> + "Your session expired. Sign out and back in to keep going." is RelayException.NotFound -> "This server doesn't look like a Flamenet relay." is RelayException.RateLimited -> "Too many attempts. Wait a few minutes." is Vault.Corrupt -> "This phone's stored keys could not be read."