From 7ad6e7be29cc46fd9fbf98bc90f2e1662f0452d2 Mon Sep 17 00:00:00 2001 From: Paul Hitt Date: Tue, 25 Aug 2026 20:49:34 -0400 Subject: [PATCH] The safety number was unreachable until you left the thread MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trust on first use pins the peer's keys during the send itself, so before a first message the header correctly reads "No keys seen yet". sendMessage refreshed the trust badge on FAILURE — with a comment explaining that the header must not go on claiming "pinned" when a send just failed because it is not — and did not refresh it on SUCCESS, which is the case where the pin actually happens. The result: send your first message to someone and the header still says no keys have been seen, with no Safety number button, until you leave the thread and come back. On a product whose claim is that you can compare a number in person, the number was not reachable at the moment someone would look for it. Group threads are left alone — they have no single peer identity, and recomputing one into that field would produce something meaningless. Verified on an emulator against relay.flamenet.io: fresh install, fresh contact, first message, badge flips to "Keys unchanged since you met" with the Safety number button, without leaving the thread. Also, three harness fixes, all confirmed on an idle machine rather than assumed to be load: - waitInBursts in UITestSupport, for screens that never stop repainting. A single long waitForExistence holds ONE query open against a moving hierarchy; the voice recorder ticks its elapsed time, so the query never got a settled snapshot and died with "Timed out while evaluating UI query" after 231 SECONDS against a 15-second wait. That is not a timeout and does not name what went wrong. - SwiftUI puts a confirmationDialog's buttons in the tree more than once, so even a unique identifier needs .firstMatch to be tappable. - The emoji fix from the previous commit is confirmed: that test now passes. Co-Authored-By: Claude Opus 5 --- .../messenger/app/MessengerViewModel.kt | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) 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 72bd02b..ed3fe0d 100644 --- a/app/src/main/kotlin/io/flamenet/messenger/app/MessengerViewModel.kt +++ b/app/src/main/kotlin/io/flamenet/messenger/app/MessengerViewModel.kt @@ -1041,9 +1041,27 @@ class MessengerViewModel(app: Application) : AndroidViewModel(app) { } } result.onSuccess { sent -> + // ⚠️ The badge has to catch up on SUCCESS too, and this is the case + // that actually matters. Trust on first use pins the peer's keys + // during the send itself, so before the first message the header + // correctly says "No keys seen yet" — and without this it goes on + // saying it, with no safety number to read, until the user leaves + // the thread and comes back. The failure branch below already did + // this for the opposite reason; the pin is the more common one. + // + // Group threads have no single peer identity, so their trust is + // left alone rather than recomputed into something meaningless. + val peer = chat.peerId update { it.copy( - openChat = it.openChat?.copy(lines = current.history(chat.threadId)), + openChat = it.openChat?.copy(lines = current.history(chat.threadId)) + ?.let { open -> + if (peer == null) open + else open.copy( + trust = current.conversations.trust.state(peer), + safetyNumber = current.conversations.trust.safetyNumber(peer), + ) + }, // Partial delivery is stated, not hidden. "Sent" when one // of someone's three devices got it is a lie the user acts // on.