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:
co-authored by
Claude Opus 4.8
parent
5f7e8dca80
commit
f92e442bfc
@@ -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
|
||||
|
||||
@@ -560,6 +560,20 @@ function fedDidKeySuffix(did: string): string | null {
|
||||
}
|
||||
|
||||
function mergeKeyForPeer(peer: MeshPeer): { key: string; matchedFed: FedNodeInfo | null } {
|
||||
// Strongest signal: the verified archipelago identity key. The backend binds
|
||||
// it onto BOTH twins (federation peer natively, radio twin via
|
||||
// `bind_federation_twins`), so grouping by it collapses the cross-transport
|
||||
// duplicate regardless of advert name — this is what survives the Meshtastic
|
||||
// device rename, which broke the `Archy-z6Mk…` did-prefix match below. Prefer
|
||||
// the matching federation node's `did:` key so this stays consistent with the
|
||||
// federation-only placeholder pass (which dedups on `did:<did>`); fall back to
|
||||
// an `arch:` key only when no federation entry is known for the identity.
|
||||
if (peer.arch_pubkey_hex) {
|
||||
for (const fed of fedNodesByDid.value.values()) {
|
||||
if (fed.pubkey === peer.arch_pubkey_hex) return { key: `did:${fed.did}`, matchedFed: fed }
|
||||
}
|
||||
return { key: `arch:${peer.arch_pubkey_hex.toLowerCase()}`, matchedFed: null }
|
||||
}
|
||||
if (peer.did) return { key: `did:${peer.did}`, matchedFed: fedNodesByDid.value.get(peer.did) ?? null }
|
||||
// pubkey cross-ref: a federation node may share the archipelago pubkey
|
||||
// with this radio peer if it's the same physical node (rare today, since
|
||||
|
||||
Reference in New Issue
Block a user