From d9afaf45fb781f697e20db954cd7d28722cfbdd3 Mon Sep 17 00:00:00 2001 From: Strycher Date: Mon, 27 Jul 2026 02:05:24 -0400 Subject: [PATCH] fix(#383): don't attribute room reactions to the room's own name Adversarial review caught a data-loss defect in the reactor-name fallback. For a reaction in a room from an author not in the local contacts, _resolveContactSenderName returns null and the chain fell through to reactionContact?.name. But reactionContact is the room server, not the reactor, so every unknown-author reaction was attributed to the room's display name. Two distinct unknown authors then both resolved to that one name, so applyReaction's per-reactor dedup treated the second as a duplicate and dropped it, count and all. Fall back to the per-author hex (from the frame's own fourByteRoomContact Key) instead, which is unique per reactor. In a true 1:1 that hex is empty and the contact genuinely is the reactor, so its name stays the correct fallback. Co-Authored-By: Claude Opus 4.8 --- lib/connector/meshcore_connector.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index 63f33e2..e87e6af 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -6563,10 +6563,18 @@ class MeshCoreConnector extends ChangeNotifier { final reactingAuthorHex = message.fourByteRoomContactKey .map((b) => b.toRadixString(16).padLeft(2, '0')) .join(); + // In a room, reactionContact is the room server, NOT the reactor, so its + // name must never be the fallback: it would attribute every unknown-author + // reaction to the room and then dedup distinct authors into one, dropping + // reactions (#383). Fall back to the per-author hex, which is unique per + // reactor. In a true 1:1 the author hex is empty and the contact IS the + // reactor, so its name is correct. + final fallbackName = (isRoomReaction && reactingAuthorHex.isNotEmpty) + ? reactingAuthorHex + : (reactionContact?.name ?? pubKeyHex); final reactorName = _resolveContactSenderName(message, reactionContact, isRoomReaction) ?? - reactionContact?.name ?? - (reactingAuthorHex.isNotEmpty ? reactingAuthorHex : pubKeyHex); + fallbackName; final reactionIdentifier = '${reactionInfo.targetHash}_${reactionInfo.emoji}_$reactingAuthorHex';