fix(threads): review-wave fixes — decryption re-render, receipt dedupe, chip perf
Two-reviewer audit of the thread stack; confirmed findings fixed: - ThreadTimeline: wrap encrypted events in EncryptedContent so a live-arriving E2EE reply re-renders when its key decrypts (decryption emits neither RoomEvent.Timeline nor ThreadEvent.Update — previously stuck at "Unable to decrypt"). - ThreadPanel: mark-read deduped on the latest event id (RoomEvent.Timeline re-emits per backfilled event/edit/reaction; previously up to N receipt POSTs per panel open) + rejection handled with retry. - RoomTimeline: ThreadSummary chips now mount only for events carrying thread data (each chip holds a room-level listener; one per rendered message would blow the SDK's 100-listener emitter cap) with a single room-level ThreadEvent.New tick for new-thread liveness. - useThreadPendingEvents: keep a sent reply visible through the /send-response→ /sync window (was flashing out of the pending strip before landing). - ThreadTimeline: reseed the window on RoomEvent.TimelineReset (gappy sync left a detached timeline). Documented-acceptable (reviewer-noted): thread typing shows as room typing (no per-thread typing in the spec; Element matches), thread panel + members drawer can be open together, scheduled-send is thread-unaware but unreachable there. Gates: tsc clean, eslint 0 errors, build OK, 616/617 tests (1 IDB skip). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,9 +18,11 @@ import {
|
||||
IContent,
|
||||
MatrixClient,
|
||||
MatrixEvent,
|
||||
RelationType,
|
||||
Room,
|
||||
RoomEvent,
|
||||
RoomEventHandlerMap,
|
||||
ThreadEvent,
|
||||
} from 'matrix-js-sdk';
|
||||
import { HTMLReactParserOptions } from 'html-react-parser';
|
||||
import classNames from 'classnames';
|
||||
@@ -477,6 +479,19 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
|
||||
const setReplyDraft = useSetAtom(roomIdToReplyDraftAtomFamily(room.roomId));
|
||||
const setActiveThreadId = useSetAtom(roomIdToActiveThreadIdAtomFamily(room.roomId));
|
||||
// Thread summary chips only mount for events that already carry thread data
|
||||
// (perf: a chip subscribes room-level listeners, so mounting one per rendered
|
||||
// message would exceed the SDK's emitter cap). This single room-level
|
||||
// ThreadEvent.New subscription re-renders the timeline once when a brand-new
|
||||
// thread appears, so the root's chip shows up without unrelated activity.
|
||||
const [, setThreadNewTick] = useState(0);
|
||||
useEffect(() => {
|
||||
const handleThreadNew = () => setThreadNewTick((c) => c + 1);
|
||||
room.on(ThreadEvent.New, handleThreadNew);
|
||||
return () => {
|
||||
room.removeListener(ThreadEvent.New, handleThreadNew);
|
||||
};
|
||||
}, [room]);
|
||||
const powerLevels = usePowerLevelsContext();
|
||||
const creators = useRoomCreators(room);
|
||||
|
||||
@@ -1136,9 +1151,11 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
onReactionToggle={handleReactionToggle}
|
||||
/>
|
||||
)}
|
||||
{(!threadRootId || threadRootId === mEventId) && (
|
||||
<ThreadSummary rootEvent={mEvent} room={room} onOpen={setActiveThreadId} />
|
||||
)}
|
||||
{(!threadRootId || threadRootId === mEventId) &&
|
||||
(mEvent.getThread() !== undefined ||
|
||||
mEvent.getServerAggregatedRelation(RelationType.Thread) !== undefined) && (
|
||||
<ThreadSummary rootEvent={mEvent} room={room} onOpen={setActiveThreadId} />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
hideReadReceipts={hideActivity}
|
||||
@@ -1227,9 +1244,11 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
|
||||
onReactionToggle={handleReactionToggle}
|
||||
/>
|
||||
)}
|
||||
{(!threadRootId || threadRootId === mEventId) && (
|
||||
<ThreadSummary rootEvent={mEvent} room={room} onOpen={setActiveThreadId} />
|
||||
)}
|
||||
{(!threadRootId || threadRootId === mEventId) &&
|
||||
(mEvent.getThread() !== undefined ||
|
||||
mEvent.getServerAggregatedRelation(RelationType.Thread) !== undefined) && (
|
||||
<ThreadSummary rootEvent={mEvent} room={room} onOpen={setActiveThreadId} />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
hideReadReceipts={hideActivity}
|
||||
|
||||
Reference in New Issue
Block a user