@@ -306,12 +306,17 @@ class ChatActions {
306306 // the message is linked to the contact-aware handle instead of creating
307307 // an orphaned duplicate.
308308 final addr = inputMessage.handle! .address;
309- final altAddr = addr.startsWith ('+' ) ? addr.substring (1 ) : '+$addr ' ;
310- final altUAS = '$altAddr /${inputMessage .handle !.service }' ;
311- final altQuery = handleBox.query (Handle_ .uniqueAddressAndService.equals (altUAS)).build ();
312- altQuery.limit = 1 ;
313- final altHandle = altQuery.findFirst ();
314- altQuery.close ();
309+ // Only phone numbers have a +/− prefix variant. Skip emails — an '@'
310+ // address would otherwise produce a nonsensical "+user@example.com" query.
311+ Handle ? altHandle;
312+ if (! addr.contains ('@' )) {
313+ final altAddr = addr.startsWith ('+' ) ? addr.substring (1 ) : '+$addr ' ;
314+ final altUAS = '$altAddr /${inputMessage .handle !.service }' ;
315+ final altQuery = handleBox.query (Handle_ .uniqueAddressAndService.equals (altUAS)).build ();
316+ altQuery.limit = 1 ;
317+ altHandle = altQuery.findFirst ();
318+ altQuery.close ();
319+ }
315320
316321 if (altHandle != null ) {
317322 handleToLink = altHandle;
@@ -403,12 +408,30 @@ class ChatActions {
403408 needsUpdate = true ;
404409 }
405410
406- // Keep chat participant list in sync when we resolve a new sender
407- if (handleToLink != null &&
408- dbChat != null &&
409- ! dbChat.handles.any ((h) => h.originalROWID == handleToLink! .originalROWID)) {
410- dbChat.handles.add (handleToLink);
411- dbChat.handles.applyToDb ();
411+ // Keep the chat's participant list in sync with the resolved sender and
412+ // collapse any duplicate entries. De-dupe on the handle's real identity
413+ // (address + service, its unique key) as well as originalROWID: a
414+ // message's sender handle can carry a null/mismatched originalROWID
415+ // versus the copy already linked to the chat, and an originalROWID-only
416+ // guard both re-adds the same number and never removes a pre-existing
417+ // duplicate (ToMany is a list, so the same handle can appear twice).
418+ if (handleToLink != null && dbChat != null ) {
419+ bool sameIdentity (Handle a, Handle b) =>
420+ (a.originalROWID != null && a.originalROWID == b.originalROWID) ||
421+ (a.address == b.address && a.service == b.service);
422+ final current = List <Handle >.from (dbChat.handles);
423+ final deduped = < Handle > [];
424+ for (final h in current) {
425+ if (! deduped.any ((d) => sameIdentity (d, h))) deduped.add (h);
426+ }
427+ final hadDuplicate = deduped.length != current.length;
428+ final senderMissing = ! deduped.any ((d) => sameIdentity (d, handleToLink! ));
429+ if (senderMissing) deduped.add (handleToLink);
430+ if (hadDuplicate || senderMissing) {
431+ dbChat.handles.clear ();
432+ dbChat.handles.addAll (deduped);
433+ dbChat.handles.applyToDb ();
434+ }
412435 }
413436
414437 // Process and link attachments if present
0 commit comments