fix(timeline): thread panel shows read receipts; receipts recompute incrementally
- ReadPositionsContext is provided once at Room level so the thread panel (a sibling of RoomView) gets real positions instead of the empty default; own thread messages no longer sit on "Sent" forever (#38). - Receipt events only recompute the users they name, merged into the previous map with reference equality preserved for untouched rows, so a receipt no longer re-renders every message (#40). Unit-tested. Fixes #38 Fixes #40 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
@@ -28,6 +28,8 @@ import { roomIdToActiveThreadIdAtomFamily } from '../../state/room/thread';
|
||||
import { threadsListAtom } from '../../state/threadsList';
|
||||
import { ThreadPanel } from './thread';
|
||||
import { ThreadsListPanel } from './thread/ThreadsListPanel';
|
||||
import { ReadPositionsContext } from './ReadPositionsContext';
|
||||
import { useRoomReadPositions } from '../../hooks/useRoomReadPositions';
|
||||
|
||||
export function Room() {
|
||||
const { eventId } = useParams();
|
||||
@@ -52,6 +54,10 @@ export function Room() {
|
||||
const powerLevels = usePowerLevels(room);
|
||||
const members = useRoomMembers(mx, room.roomId);
|
||||
const chat = useAtomValue(callChatAtom);
|
||||
// Hoisted here (instead of inside RoomTimeline) so ThreadPanel/ThreadTimeline —
|
||||
// rendered as siblings of RoomView, not descendants — share the same computed
|
||||
// positions instead of falling back to the context default (Gitea #38).
|
||||
const readPositions = useRoomReadPositions(room);
|
||||
useKeyDown(
|
||||
window,
|
||||
useCallback(
|
||||
@@ -147,87 +153,89 @@ export function Room() {
|
||||
|
||||
return (
|
||||
<PowerLevelsContextProvider value={powerLevels}>
|
||||
<Box grow="Yes">
|
||||
{callView && (screenSize === ScreenSize.Desktop || !chat) && (
|
||||
<Box grow="Yes" direction="Column">
|
||||
<RoomViewHeader callView />
|
||||
<Box grow="Yes">
|
||||
<CallView />
|
||||
<ReadPositionsContext.Provider value={readPositions}>
|
||||
<Box grow="Yes">
|
||||
{callView && (screenSize === ScreenSize.Desktop || !chat) && (
|
||||
<Box grow="Yes" direction="Column">
|
||||
<RoomViewHeader callView />
|
||||
<Box grow="Yes">
|
||||
<CallView />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
{!callView && (
|
||||
<Box grow="Yes" direction="Column">
|
||||
<RoomViewHeader />
|
||||
<Box grow="Yes">
|
||||
<RoomView eventId={eventId} />
|
||||
)}
|
||||
{!callView && (
|
||||
<Box grow="Yes" direction="Column">
|
||||
<RoomViewHeader />
|
||||
<Box grow="Yes">
|
||||
<RoomView eventId={eventId} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
)}
|
||||
|
||||
{callView && chat && (
|
||||
<>
|
||||
{screenSize === ScreenSize.Desktop && (
|
||||
<Line variant="Background" direction="Vertical" size="300" />
|
||||
)}
|
||||
<CallChatView />
|
||||
</>
|
||||
)}
|
||||
{showGallery && (
|
||||
<>
|
||||
{screenSize === ScreenSize.Desktop && (
|
||||
<Line variant="Background" direction="Vertical" size="300" />
|
||||
)}
|
||||
<MediaGallery key={room.roomId} room={room} onClose={() => setGalleryOpen(false)} />
|
||||
</>
|
||||
)}
|
||||
{showWidgets && (
|
||||
<>
|
||||
{screenSize === ScreenSize.Desktop && (
|
||||
<Line variant="Background" direction="Vertical" size="300" />
|
||||
)}
|
||||
<WidgetsPanel
|
||||
key={room.roomId}
|
||||
room={room}
|
||||
requestClose={() => setWidgetsOpen(false)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{showThreadsList && (
|
||||
<>
|
||||
{screenSize === ScreenSize.Desktop && (
|
||||
<Line variant="Background" direction="Vertical" size="300" />
|
||||
)}
|
||||
<ThreadsListPanel
|
||||
key={room.roomId}
|
||||
room={room}
|
||||
onClose={closeThreadsList}
|
||||
onOpenThread={openThreadFromList}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{showThreadPanel && activeThreadId && (
|
||||
<>
|
||||
{screenSize === ScreenSize.Desktop && (
|
||||
<Line variant="Background" direction="Vertical" size="300" />
|
||||
)}
|
||||
<ThreadPanel
|
||||
key={`${room.roomId}${activeThreadId}`}
|
||||
room={room}
|
||||
threadId={activeThreadId}
|
||||
requestClose={() => setActiveThreadId(null)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{showMembers && (
|
||||
<>
|
||||
{screenSize === ScreenSize.Desktop && (
|
||||
<Line variant="Background" direction="Vertical" size="300" />
|
||||
)}
|
||||
<MembersDrawer key={room.roomId} room={room} members={members} />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
{callView && chat && (
|
||||
<>
|
||||
{screenSize === ScreenSize.Desktop && (
|
||||
<Line variant="Background" direction="Vertical" size="300" />
|
||||
)}
|
||||
<CallChatView />
|
||||
</>
|
||||
)}
|
||||
{showGallery && (
|
||||
<>
|
||||
{screenSize === ScreenSize.Desktop && (
|
||||
<Line variant="Background" direction="Vertical" size="300" />
|
||||
)}
|
||||
<MediaGallery key={room.roomId} room={room} onClose={() => setGalleryOpen(false)} />
|
||||
</>
|
||||
)}
|
||||
{showWidgets && (
|
||||
<>
|
||||
{screenSize === ScreenSize.Desktop && (
|
||||
<Line variant="Background" direction="Vertical" size="300" />
|
||||
)}
|
||||
<WidgetsPanel
|
||||
key={room.roomId}
|
||||
room={room}
|
||||
requestClose={() => setWidgetsOpen(false)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{showThreadsList && (
|
||||
<>
|
||||
{screenSize === ScreenSize.Desktop && (
|
||||
<Line variant="Background" direction="Vertical" size="300" />
|
||||
)}
|
||||
<ThreadsListPanel
|
||||
key={room.roomId}
|
||||
room={room}
|
||||
onClose={closeThreadsList}
|
||||
onOpenThread={openThreadFromList}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{showThreadPanel && activeThreadId && (
|
||||
<>
|
||||
{screenSize === ScreenSize.Desktop && (
|
||||
<Line variant="Background" direction="Vertical" size="300" />
|
||||
)}
|
||||
<ThreadPanel
|
||||
key={`${room.roomId}${activeThreadId}`}
|
||||
room={room}
|
||||
threadId={activeThreadId}
|
||||
requestClose={() => setActiveThreadId(null)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{showMembers && (
|
||||
<>
|
||||
{screenSize === ScreenSize.Desktop && (
|
||||
<Line variant="Background" direction="Vertical" size="300" />
|
||||
)}
|
||||
<MembersDrawer key={room.roomId} room={room} members={members} />
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</ReadPositionsContext.Provider>
|
||||
</PowerLevelsContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -90,8 +90,6 @@ import { useSetting } from '../../state/hooks/settings';
|
||||
import { MessageLayout, settingsAtom } from '../../state/settings';
|
||||
import { useMatrixEventRenderer } from '../../hooks/useMatrixEventRenderer';
|
||||
import { Reactions, Message, Event, EncryptedContent } from './message';
|
||||
import { ReadPositionsContext } from './ReadPositionsContext';
|
||||
import { useRoomReadPositions } from '../../hooks/useRoomReadPositions';
|
||||
import { useMemberEventParser } from '../../hooks/useMemberEventParser';
|
||||
import * as customHtmlCss from '../../styles/CustomHtml.css';
|
||||
import { RoomIntro } from '../../components/room-intro';
|
||||
@@ -463,7 +461,8 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
const [messageSpacing] = useSetting(settingsAtom, 'messageSpacing');
|
||||
const [legacyUsernameColor] = useSetting(settingsAtom, 'legacyUsernameColor');
|
||||
const direct = useIsDirectRoom();
|
||||
const readPositions = useRoomReadPositions(room);
|
||||
// Read positions are computed once in Room.tsx and provided via ReadPositionsContext
|
||||
// so both RoomTimeline and ThreadTimeline consume the same value (Gitea #38).
|
||||
const [hideMembershipEvents] = useSetting(settingsAtom, 'hideMembershipEvents');
|
||||
const [hideNickAvatarEvents] = useSetting(settingsAtom, 'hideNickAvatarEvents');
|
||||
const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad');
|
||||
@@ -2128,7 +2127,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
};
|
||||
|
||||
return (
|
||||
<ReadPositionsContext.Provider value={readPositions}>
|
||||
<>
|
||||
<Box grow="Yes" style={{ position: 'relative' }}>
|
||||
{unreadInfo?.readUptoEventId && !unreadInfo?.inLiveTimeline && (
|
||||
<TimelineFloat position="Top">
|
||||
@@ -2264,6 +2263,6 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
onClose={() => setEditHistoryEvent(undefined)}
|
||||
/>
|
||||
)}
|
||||
</ReadPositionsContext.Provider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user