fix(unread): stop DM device-verification requests re-lighting as unread
CI / Build & Quality Checks (push) Successful in 11m11s
CI / Trigger Desktop Build (push) Successful in 10s

A completed in-room device-verification request is a plain m.room.message
(msgtype m.key.verification.request) that matches the default DM push rule
with no recency gate, so the server/SDK notification count stays > 0 and the
DM re-lights as unread on every fresh sync until the room is opened twice.

Two-part fix:
- Display suppression: getUnreadInfo/getUnreadInfos return {0,0} for a room
  whose ENTIRE unread span (tail -> read receipt) is verification-flow events,
  via new pure helpers isVerificationFlowEvent + unreadIsOnlyVerification.
  Conservative: never suppresses when the read marker is off-window, the tail
  is still encrypted, or a highlight is present.
- Durable auto-read: useAutoMarkVerificationRead sends a read receipt covering
  the request (the only SDK-durable lever), once per room per session, gated on
  the same verification-only predicate so it can never ack a real message.

unreadIsOnlyVerification also rejects any room with an unread thread, because
markAsRead clears every thread unconditionally — otherwise a verification-only
main timeline with a genuine unread thread reply would be hidden/auto-acked.

Reviewed by 5 agents; the thread-scope guard closes the one bug they found.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 01:18:31 -04:00
co-authored by Claude Opus 4.8
parent 1176bea0ee
commit b2678d5c6d
6 changed files with 277 additions and 6 deletions
+1 -1
View File
@@ -531,7 +531,7 @@ function MessageNotifications() {
// thread path is already gated by shouldNotifyThreadReply, so it must NOT
// re-gate on the room count — otherwise an explicit per-thread "All replies"
// override in a Mentions-only room is silently dropped.
if (!threadId && getUnreadInfo(room).total === 0) return;
if (!threadId && getUnreadInfo(room, undefined, mx).total === 0) return;
lastNotifiedEventRef.current.set(dedupeKey, eventId);