fix(notifications/threads): Wave-1 audit fixes (🔴 + web 🟠)
- T1 (🔴): markThreadAsRead no longer receipts the thread ROOT (a 2nd instance of the read-marker-corruption regression — opening a thread whose root is old re-lit the whole room). Extracted to a pure threadReceipt.ts + 5 regression tests. - N1 (🔴): favicon/tab-title unread count now sums only leaf rooms (was double- counting every ancestor-space aggregate in roomToUnread). - N2 (🔴): notifications/sounds dedupe on the event id, not the unread count — fixes "read a DM, next message never notifies again". - T4 (🟠): the thread notification path no longer re-gates on the room count, so an explicit per-thread "All replies" override in a Mentions-only room fires. - N3 (🟠): getUnreadInfos skips phantom {0,0} entries (muted-thread-only rooms no longer light the nav row / pollute unread filters). - N4 (🟠): the Receipt handler recomputes unread instead of blanket-DELETE, so a threaded receipt can't wipe a room's valid main-timeline badge. - T2 (🟠): thread "Jump to Latest" re-anchors the virtual window (was landing on a stale mid/old event). Gates: tsc/eslint/prettier clean, build OK, 678 tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,6 @@ import {
|
||||
EventTimeline,
|
||||
MatrixClient,
|
||||
MatrixEvent,
|
||||
ReceiptType,
|
||||
Room,
|
||||
RoomEvent,
|
||||
RoomEventHandlerMap,
|
||||
@@ -146,32 +145,6 @@ export const useThreadPendingEvents = (
|
||||
return pending;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a threaded read receipt up to the latest confirmed event in the thread.
|
||||
*
|
||||
* The receipt is threaded by default (scoped to this thread), which clears the
|
||||
* per-thread unread count. Mirrors the latest-valid-event scan in
|
||||
* `utils/notifications.ts`.
|
||||
*/
|
||||
export const markThreadAsRead = async (
|
||||
mx: MatrixClient,
|
||||
thread: Thread,
|
||||
privateReceipt: boolean,
|
||||
): Promise<void> => {
|
||||
const events = thread.liveTimeline.getEvents();
|
||||
|
||||
let latestEvent: MatrixEvent | undefined;
|
||||
for (let i = events.length - 1; i >= 0; i -= 1) {
|
||||
const evt = events[i];
|
||||
if (evt && !evt.isSending()) {
|
||||
latestEvent = evt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!latestEvent) return;
|
||||
|
||||
await mx.sendReadReceipt(
|
||||
latestEvent,
|
||||
privateReceipt ? ReceiptType.ReadPrivate : ReceiptType.Read,
|
||||
);
|
||||
};
|
||||
// markThreadAsRead moved to ./threadReceipt (pure + unit-tested); re-exported
|
||||
// here for existing import sites.
|
||||
export { markThreadAsRead } from './threadReceipt';
|
||||
|
||||
Reference in New Issue
Block a user