Private
Public Access
Fix desktop tray unread indicator: clear only on genuine room entry
The tray indicator must stay visible while a message is unread and clear only when the user actually opens the room containing it -- never merely because the window was shown, focused, restored, or opened from the tray. Adds window.dsDesktop.roomOpened() (called exactly once per genuine transition to a real room id, via useDesktopRoomBridge) and wires up window.dsDesktop.setUnreadCount() (declared but never actually called before this) to fire whenever the unread-room count changes. Both are no-ops in browser builds, matching this bridge's existing optional- chaining convention. Frontend unread state stays authoritative -- clearRoomIndicators is pulled out of ChatShellPage as a pure function (same behavior, now directly testable) so opening one room only clears that room's unread/mention flags, leaving every other room's state untouched. Also adds a frontend test runner (vitest + React Testing Library) -- none existed in this project before. 10 new tests cover: roomOpened firing once on entry, not refiring on an unrelated rerender with the same room id, refiring on a genuine room-to-room transition, never firing with no room id, setUnreadCount firing independently of roomOpened, clearing one room preserving every other room's unread state, and the desktop bridge remaining fully optional. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
047f47d524
commit
3349cadea2
9 files changed
+1155
-16
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { NetworkError } from '../api/client'
|
||||
import { listMyRooms, listRoomMembers } from '../api/rooms'
|
||||
@@ -12,7 +12,9 @@ import { Sidebar } from '../components/Sidebar'
|
||||
import { TopBar } from '../components/TopBar'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
import { useChatSocketContext } from '../context/ChatSocketContext'
|
||||
import { useDesktopRoomBridge } from '../hooks/useDesktopRoomBridge'
|
||||
import { MOBILE_BREAKPOINT, useWindowWidth } from '../hooks/useWindowWidth'
|
||||
import { clearRoomIndicators } from '../lib/roomIndicators'
|
||||
import type { MyRoomItem, RoomMember } from '../types'
|
||||
import './ChatShellPage.css'
|
||||
|
||||
@@ -60,10 +62,13 @@ export function ChatShellPage() {
|
||||
|
||||
const socket = useChatSocketContext()
|
||||
|
||||
const clearRoomIndicators = useCallback((id: string) => {
|
||||
setRooms((prev) => prev.map((r) => (r.id === id ? { ...r, has_unread: false, has_mention: false } : r)))
|
||||
const handleRoomRead = useCallback((id: string) => {
|
||||
setRooms((prev) => clearRoomIndicators(prev, id))
|
||||
}, [])
|
||||
|
||||
const unreadRoomCount = useMemo(() => rooms.filter((r) => r.has_unread).length, [rooms])
|
||||
useDesktopRoomBridge(activeRoom?.id, unreadRoomCount)
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
socket.subscribe((envelope) => {
|
||||
@@ -134,7 +139,7 @@ export function ChatShellPage() {
|
||||
onToggleInfo={() => setInfoOpen((v) => !v)}
|
||||
infoOpen={infoOpen}
|
||||
socket={socket}
|
||||
onRoomRead={clearRoomIndicators}
|
||||
onRoomRead={handleRoomRead}
|
||||
/>
|
||||
) : (
|
||||
!isMobile && (
|
||||
|
||||
Reference in new issue
Block a user