fix(mesh): collapse cross-transport twin contacts into one conversation (#12)

A node reachable both over LoRa and federation has two MeshPeer rows (radio
twin: low contact_id + firmware key; federation twin: high contact_id +
archipelago key), and messages key by peer_contact_id split across the two ids
— so opening one twin shows an empty thread (the .120->.89 symptom).

- backend: new group_peer_twins() helper groups peers by arch_pubkey_hex (set on
  BOTH twins by bind_federation_twins), keeps the radio id as the mesh-first
  send target, and unions messages across all twin ids. Wired into
  conversations.list / conversations.messages / mesh.contacts-list. +3 unit tests.
- frontend: the live chat list merges client-side (mergedPeers) and matched twins
  by the "Archy-z6Mk..." advert prefix, which the Meshtastic device rename broke
  (radio now advertises the server name). Merge by arch_pubkey_hex instead, which
  the backend reliably sets on both twins. Expose arch_pubkey_hex on MeshPeer.
- fix unrelated stale test: EcashTransaction test missing the new `kind` field.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-06-20 08:01:14 -04:00
co-authored by Claude Opus 4.8
parent 5f7e8dca80
commit f92e442bfc
6 changed files with 180 additions and 6 deletions
+6
View File
@@ -26,6 +26,12 @@ export interface MeshPeer {
advert_name: string
did: string | null
pubkey_hex: string | null
/** Verified archipelago ed25519 identity key. The backend binds this onto
* BOTH a node's twins — the federation peer natively and the radio twin via
* `bind_federation_twins` — so it's the most reliable key for collapsing the
* cross-transport duplicate (survives the device rename, unlike the advert
* name). Absent on radio peers that were never matched to a federation twin. */
arch_pubkey_hex?: string | null
rssi: number | null
snr: number | null
last_heard: string