* fix(chat): correct unread — archived sessions + mount-race auto-read (MUL-4360)
Reland of #5315, which was reverted by #5332 as collateral in an unrelated
release-wide revert (to unwind 162/163 migration BLOCK from other PRs), not for
any defect in this code — Howard/Preflight assessed these changes WARN /
non-blocking. Restores all three fixes verbatim off current main:
- backend: ListAllChatSessionsByCreator derives unread_count=0 / has_unread=false
for status='archived' rows via a CASE gate. last_read_at is untouched, so
unarchiving restores the true unread state. Single source of truth for every
unread surface (quick-chat FAB, sidebar Chat tab, chat-window header, mobile);
installed desktop clients benefit with no app update.
- frontend: the archive mutation onMutate optimistically zeroes the row's unread
so no badge counts a just-archived session in the frame before the refetch
lands. Unarchive does not fabricate a count — the true state returns from the
server refetch.
- frontend: auto-mark-read is deferred a tick and cancelled on cleanup, so a
session that is only momentarily active on mount (persisted activeSessionId
restored for one frame, then cleared by the URL->store effect) is not marked
read; only a session that stays active past the tick is.
Verification: sqlc regenerate produces no drift; go test ./internal/handler
-run 'TestListChatSessions_ArchivedSessionReportsZeroUnread|TestSetChatSessionArchived_ClearsChannelBinding'
passes against a real Postgres; vitest mutations.test.tsx (3) and
use-chat-controller.test.tsx (8) pass; core + views typecheck clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
* fix(chat): converge archived unread on chat:session_updated realtime event (MUL-4360)
Howard's #5333 review found a real cross-tab gap: the chat:session_updated
handler patched status but never unread, and chatSessionsOptions is
staleTime: Infinity, so a session archived in one tab kept its unread badge lit
in another tab/device forever — the same stuck-badge bug, one surface over.
Extract the inline handler into applyChatSessionUpdatedToCache and force
unread_count=0 / has_unread=false when payload.status === "archived", mirroring
the archive mutation's optimistic patch and the backend deriving unread=0 for
archived rows. Unarchive does NOT fabricate a count — the true unread returns
from the server refetch (last_read_at untouched). No sessions-list
invalidation; minimal field patch as reviewed.
Adds use-realtime-sync.test.ts coverage: an archived event zeroes a cached
unread row; an active event does not resurrect unread; a rename-only event
leaves unread untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>