eleven / messenger Public

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>
James Adam da3c078bf5f5781f2447fb5b9585fdc9a5017a64 parent b5b4be9
2 files changed, +21 −9
  • ios/LChatCore/Sources/LChatCore/ThreadStore.swift +15 −8
  • ios/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(