ios+mac: a trailing reaction stays silent in the sidebar
The native sibling of the web fix in #520: ThreadStore decrypted a thread's last_blob and passed it to previewText unconditionally, so a thread whose newest event is a reaction showed a blank "Name: " preview (groups) or an empty one (DMs), and someone else's reaction lit the unread dot. Reaction payloads now leave the preview empty and suppress the dot, mirroring the web's load path. Also: the Mac's decorated (communication-style) reaction notification passed previewText as its message line — blank for a reaction payload; it now uses the un-attributed reaction form. Verified: LChat scheme tests pass in the iPhone 16 Pro simulator (includes the event-vector suite); LChatMac builds. No changelog entry — native changes hold theirs until the release that ships them.
Co-Authored-By: Claude <noreply@anthropic.com>
2 files changed,
+21
−9
ios/LChatCore/Sources/LChatCore/ThreadStore.swift+15 −8ios/LChatMac/NativeChatView.swift+6 −1
diff --git a/ios/LChatCore/Sources/LChatCore/ThreadStore.swift b/ios/LChatCore/Sources/LChatCore/ThreadStore.swift| index 3be63e4..6316a12 100644 |
| --- a/ios/LChatCore/Sources/LChatCore/ThreadStore.swift |
| +++ b/ios/LChatCore/Sources/LChatCore/ThreadStore.swift |
| @@ -199,14 +199,21 @@ public final class ThreadStore: ObservableObject { |
| var lastMine = false |
| if let lb = t.last_blob, !lb.isEmpty, let key, |
| let p = try? key.decryptJSON(MessagePayload.self, blob: lb) { |
| - preview = previewText(kind: t.kind, payload: p) |
| - // A chat whose latest message is ours isn't unread — sending from |
| - // any device means we've read it. Old payloads have no uid; fall |
| - // back to the screen name (mirrors ownMessage in web/static/ui.js). |
| - if let uid = p.uid, !myUserId.isEmpty { |
| - lastMine = uid == myUserId |
| - } else if let n = p.name, !n.isEmpty { |
| - lastMine = n == rosterLabels[myUserId] |
| + if p.isReactionEvent { |
| + // A trailing reaction stays silent in the sidebar — no |
| + // preview (previewText would render a blank "Name: ") and |
| + // no unread dot, mirroring the web's load path (shell.js). |
| + lastMine = true |
| + } else { |
| + preview = previewText(kind: t.kind, payload: p) |
| + // A chat whose latest message is ours isn't unread — sending from |
| + // any device means we've read it. Old payloads have no uid; fall |
| + // back to the screen name (mirrors ownMessage in web/static/ui.js). |
| + if let uid = p.uid, !myUserId.isEmpty { |
| + lastMine = uid == myUserId |
| + } else if let n = p.name, !n.isEmpty { |
| + lastMine = n == rosterLabels[myUserId] |
| + } |
| } |
| } |
| // An app thread with no activity yet shows its blurb instead of a |
diff --git a/ios/LChatMac/NativeChatView.swift b/ios/LChatMac/NativeChatView.swift| index 4949fb3..3115966 100644 |
| --- a/ios/LChatMac/NativeChatView.swift |
| +++ b/ios/LChatMac/NativeChatView.swift |
| @@ -1213,7 +1213,12 @@ struct NativeChatView: View { |
| content, senderName: name, senderUID: p.uid, |
| origin: origin, conversationID: tid, |
| groupName: thread.kind == "group" ? thread.title : nil, |
| - messageText: previewText(kind: "dm", payload: p)) { |
| + // The decorated banner names the sender itself, so reactions |
| + // take the un-attributed form (previewText would be blank — |
| + // a reaction payload has no text). |
| + messageText: p.isReactionEvent |
| + ? "Reacted \(p.emoji ?? "") to your message" |
| + : previewText(kind: "dm", payload: p)) { |
| deliver = styled |
| } |
| UNUserNotificationCenter.current().add( |