mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-22 17:49:48 +02:00
The aggregate chat unread badge used two different definitions: web/ desktop's sidebar summed unread messages while mobile's tab badge (and the since-removed ChatFab badge it mirrored) counted sessions — the same account state showed e.g. 5 on web and 2 on iOS. The sidebar also summed ALL sessions while the thread list zeroes the row being viewed, so a reply landing in the open conversation flashed a sidebar count with no matching row. - packages/core/chat/unread.ts: countUnreadChatMessages() as the single shared definition (IM-style message total, optional viewed-session exclusion); pure so mobile can import it. - app-sidebar: use the helper and exclude the actively-viewed session, but only while a chat surface is actually showing it (chat route or floating window open) — a remembered selection with both surfaces closed still counts, since nothing will auto mark-read there. - mobile: tab badge switches to the shared message count (99+ cap like the sidebar), ChatSessionSchema parses unread_count, and markRead's optimistic patch zeroes unread_count alongside has_unread. Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
32 lines
1.3 KiB
TypeScript
32 lines
1.3 KiB
TypeScript
import type { ChatSession } from "../types/chat";
|
|
|
|
/**
|
|
* The chat unread badge number: total unread assistant *messages* across
|
|
* sessions (IM-style), NOT the number of sessions with unread.
|
|
*
|
|
* Single source of truth for every surface that renders an aggregate chat
|
|
* unread count (web/desktop sidebar, mobile tab bar) — the platforms must
|
|
* show the same N for the same account state (see "Behavioral parity" in
|
|
* apps/mobile/CLAUDE.md). Pure function so mobile can import it directly.
|
|
*
|
|
* `excludeSessionId` drops one session from the sum: the session the user is
|
|
* currently reading. The chat thread list renders that session's own badge
|
|
* as 0 (its unread is about to be cleared by the viewer's auto mark-read),
|
|
* so an aggregate that still counted it would show a number the list can't
|
|
* account for. Callers pass it only while a chat surface is actually showing
|
|
* that session; a background pointer (e.g. a remembered selection while the
|
|
* chat page is closed) must NOT be excluded, or new replies would never
|
|
* badge.
|
|
*/
|
|
export function countUnreadChatMessages(
|
|
sessions: readonly ChatSession[] | undefined,
|
|
excludeSessionId?: string | null,
|
|
): number {
|
|
if (!sessions) return 0;
|
|
return sessions.reduce(
|
|
(sum, s) =>
|
|
s.id === excludeSessionId ? sum : sum + (s.unread_count ?? 0),
|
|
0,
|
|
);
|
|
}
|