The safety number was unreachable until you left the thread

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 <noreply@anthropic.com>
This commit is contained in:
Paul Hitt
2026-08-25 20:49:34 -04:00
co-authored by Claude Opus 5
parent 053cefbce4
commit 7ad6e7be29
@@ -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.