import { MatrixClient, ReceiptType, Thread } from 'matrix-js-sdk'; /** * Send a threaded read receipt for a thread, clearing its per-thread unread * count. * * CRITICAL: never receipt the thread ROOT. A thread's liveTimeline is * `[root, reply1, …]`, so the latest event IS the root when replies aren't * loaded yet (common — the thread panel fires this on mount before replies * fetch). The root is "in the main timeline", so a receipt for it is written by * the SDK with `thread_id:"main"` at the old root, dragging the room's MAIN read * marker backwards (`getEventReadUpTo` → an old/unloaded event) and re-lighting * the whole room. We only receipt a genuine loaded reply (`thread.lastReply()`); * if none is loaded we bail (the per-thread count clears when the reply loads * and this runs again). Mirrors the root guard in `utils/notifications.ts`. * * Pure (no React/CSS) so it can be unit-tested — see `threadReceipt.test.ts`. */ export const markThreadAsRead = async ( mx: MatrixClient, thread: Thread, privateReceipt: boolean, ): Promise => { const lastReply = thread.lastReply(); if (!lastReply || lastReply.isSending() || lastReply.getId() === thread.id) return; await mx.sendReadReceipt(lastReply, privateReceipt ? ReceiptType.ReadPrivate : ReceiptType.Read); };