From bc3ce1139b0844efd8214a54b06d1dd8a5be6580 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 12 Aug 2026 12:40:52 -0400 Subject: [PATCH] fix(relay-auth): two copy defects the prompt only shows once it can fire MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both found on device once prompts started appearing again. An unloaded counterparty rendered as a shortened npub: "It won't serve posts from npub1j9hlsge8...kqy003h0 unless you log in." `User.toBestDisplayName()` falls back to `pubkeyDisplayHex()`, so the sentence presented an unrecognizable key as if it were a person's name — the one thing the reason line exists to supply. The counterparty now falls back to the generic "someone you haven't loaded yet", which says exactly as much and reads as language. The dialog *title* deliberately keeps the npub: it names the account whose identity is about to be revealed, and there an exact key beats a generic phrase. "It's also holding back 1 other thing(s)." was a `` with a parenthesised plural, which res/CLAUDE.md rules out — the noun declines on count in the Slavic and Semitic locales we ship. Now a ``. It exists only in values/, so no other locale needed converting. Co-Authored-By: Claude Opus 5 (1M context) --- .../compose/RelayAuthPromptHost.kt | 34 +++++++++++++++++-- amethyst/src/main/res/values/strings.xml | 5 ++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthPromptHost.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthPromptHost.kt index 255c095abb..40df5a15aa 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthPromptHost.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthPromptHost.kt @@ -51,6 +51,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.Placeholder import androidx.compose.ui.text.PlaceholderVerticalAlign @@ -399,7 +400,7 @@ private fun secondaryLine( ): String? { val others = purposes.filter { it !== primary } if (others.isEmpty()) return null - return stringRes(R.string.relay_auth_also_holding_back, others.size) + return pluralStringResource(R.plurals.relay_auth_also_holding_back, others.size, others.size) } /** A short label for a set of counterparties: the first person's name, or "Alice and 4 others". */ @@ -409,10 +410,39 @@ private fun counterpartyLabel( accountViewModel: AccountViewModel, ): String { val first = pubkeys.firstOrNull() ?: return "" - val name = rememberDisplayName(first, accountViewModel) + val name = rememberCounterpartyName(first, accountViewModel) return if (pubkeys.size > 1) stringRes(R.string.relay_auth_name_and_n_others, name, (pubkeys.size - 1).toString()) else name } +/** + * Like [rememberDisplayName], but never renders a bare npub. + * + * The reason sentence exists to name *a person* — "it won't serve posts from Alice". A pubkey we + * have no metadata for has no name to give, and [User.toBestDisplayName] falls back to the shortened + * npub, so the sentence became "it won't serve posts from npub1j9hlsge8…kqy003h0 unless you log in": + * a string the reader cannot recognize, dressed up as if it were a name. Falling back to the generic + * phrase says the same amount and reads as language. + * + * The dialog *title* deliberately keeps the npub ([rememberDisplayName]): it names the account whose + * identity is about to be revealed, where an unrecognizable-but-exact key still beats "someone you + * haven't loaded yet" — the user can at least match it against the account they are logged in as. + */ +@Composable +private fun rememberCounterpartyName( + pubkey: HexKey, + accountViewModel: AccountViewModel, +): String { + var user by remember(pubkey) { mutableStateOf(accountViewModel.getUserIfExists(pubkey)) } + if (user == null) { + LaunchedEffect(pubkey) { user = accountViewModel.checkGetOrCreateUser(pubkey) } + } + val loaded = user ?: return stringRes(R.string.relay_auth_someone_unloaded) + val metadata by observeUserInfo(loaded, accountViewModel) + return metadata?.info?.bestName() + ?: loaded.metadataOrNull()?.bestName() + ?: stringRes(R.string.relay_auth_someone_unloaded) +} + /** * A display name for a venue id — a public chat channel (64-hex event id), a NIP-53 live activity, * or a NIP-72 community. diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 3329e6669f..a703b1bc3e 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -1165,7 +1165,10 @@ It asks everyone to log in. Amethyst can\'t tell what it\'s holding back. someone you haven\'t loaded yet %1$s and %2$s others - It\'s also holding back %1$d other thing(s). + + It\'s also holding back %1$d other thing. + It\'s also holding back %1$d other things. + Remember for this relay Log in Not now