From efb7a68dbd8f5d4724cf8528921080494778254f Mon Sep 17 00:00:00 2001 From: 7w1 Date: Sat, 11 Jul 2026 20:06:33 -0500 Subject: [PATCH 1/2] Fix settings updates not updating timeline --- src/app/features/room/RoomTimeline.tsx | 85 +++++++++++++++++-------- src/app/hooks/useMatrixEventRenderer.ts | 6 +- 2 files changed, 61 insertions(+), 30 deletions(-) diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index d94628777..aefe9843c 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -124,6 +124,7 @@ const MemoizedTimelineItem = memo( room: Room; messageLayout: MessageLayout; messageSpacing: MessageSpacing; + settings: Record; renderMatrixEvent: ReturnType; focusItem: unknown; editId: string | undefined; @@ -231,6 +232,16 @@ const MemoizedTimelineItem = memo( if (prev.messageSpacing !== next.messageSpacing) return false; if (prev.renderMatrixEvent !== next.renderMatrixEvent) return false; + // Shallow compare settings since it contains primitive toggles + const pSettings = prev.settings as Record; + const nSettings = next.settings as Record; + if (pSettings !== nSettings) { + if (Object.keys(pSettings).length !== Object.keys(nSettings).length) return false; + for (const key in pSettings) { + if (pSettings[key] !== nSettings[key]) return false; + } + } + if (prev.focusItem !== next.focusItem) return false; if (prev.editId !== next.editId) return false; if (prev.activeReplyId !== next.activeReplyId) return false; @@ -306,21 +317,60 @@ export function RoomTimeline({ ? clientUrlPreview && encClientUrlPreview : clientUrlPreview; - const nicknames = useAtomValue(nicknamesAtom); - const globalProfiles = useAtomValue(profilesCacheAtom); - const ignoredUsersList = useIgnoredUsers(); - const ignoredUsersSet = useMemo(() => new Set(ignoredUsersList), [ignoredUsersList]); - const powerLevels = usePowerLevelsContext(); const creators = useRoomCreators(room); - const getMemberPowerTag = useGetMemberPowerTag(room, creators, powerLevels); - const permissions = useRoomPermissions(creators, powerLevels); const isReadOnly = useMemo(() => { const myPowerLevel = powerLevels?.users?.[mx.getUserId()!] ?? powerLevels?.users_default ?? 0; const sendLevel = powerLevels?.events?.['m.room.message'] ?? powerLevels?.events_default ?? 0; return myPowerLevel < sendLevel; }, [powerLevels, mx]); + const settings = useMemo(() => ({ + messageLayout, + messageSpacing, + hideReads, + showDeveloperTools, + hour24Clock, + dateFormatString, + mediaAutoLoad, + showBundledPreview, + showUrlPreview, + showClientUrlPreview, + showMaps, + autoplayStickers, + hideMemberInReadOnly, + isReadOnly, + hideMembershipEvents, + hideNickAvatarEvents, + hiddenEvents, + }), [ + messageLayout, + messageSpacing, + hideReads, + showDeveloperTools, + hour24Clock, + dateFormatString, + mediaAutoLoad, + showBundledPreview, + showUrlPreview, + showClientUrlPreview, + showMaps, + autoplayStickers, + hideMemberInReadOnly, + isReadOnly, + hideMembershipEvents, + hideNickAvatarEvents, + hiddenEvents, + ]); + + const nicknames = useAtomValue(nicknamesAtom); + const globalProfiles = useAtomValue(profilesCacheAtom); + const ignoredUsersList = useIgnoredUsers(); + const ignoredUsersSet = useMemo(() => new Set(ignoredUsersList), [ignoredUsersList]); + + const getMemberPowerTag = useGetMemberPowerTag(room, creators, powerLevels); + const permissions = useRoomPermissions(creators, powerLevels); + const [unreadInfo, setUnreadInfo] = useState(() => getRoomUnreadInfo(room, true)); const readUptoEventIdRef = useRef(undefined); @@ -737,25 +787,7 @@ export function RoomTimeline({ pushProcessor, nicknames, imagePackRooms, - settings: { - messageLayout, - messageSpacing, - hideReads, - showDeveloperTools, - hour24Clock, - dateFormatString, - mediaAutoLoad, - showBundledPreview, - showUrlPreview, - showClientUrlPreview, - showMaps, - autoplayStickers, - hideMemberInReadOnly, - isReadOnly, - hideMembershipEvents, - hideNickAvatarEvents, - hiddenEvents, - }, + settings, state: { focusItem: timelineSync.focusItem, editId, activeReplyId, openThreadId }, permissions: { canRedact: permissions.action('redact', mx.getSafeUserId()), @@ -1062,6 +1094,7 @@ export function RoomTimeline({ room={room} messageLayout={messageLayout} messageSpacing={messageSpacing} + settings={settings} renderMatrixEvent={renderMatrixEvent} focusItem={timelineSync.focusItem} editId={editId} diff --git a/src/app/hooks/useMatrixEventRenderer.ts b/src/app/hooks/useMatrixEventRenderer.ts index af34c7ce7..4f9cc5b49 100644 --- a/src/app/hooks/useMatrixEventRenderer.ts +++ b/src/app/hooks/useMatrixEventRenderer.ts @@ -1,4 +1,4 @@ -import { useCallback, useLayoutEffect, useRef } from 'react'; +import { useCallback, useRef } from 'react'; import type { ReactNode } from 'react'; export type EventRenderer = (...args: T) => ReactNode; @@ -18,9 +18,7 @@ export const useMatrixEventRenderer = ( ): RenderMatrixEvent => { const latestRef = useRef({ typeToRenderer, renderStateEvent, renderEvent }); - useLayoutEffect(() => { - latestRef.current = { typeToRenderer, renderStateEvent, renderEvent }; - }); + latestRef.current = { typeToRenderer, renderStateEvent, renderEvent }; return useCallback((eventType: string, isStateEvent: boolean, ...args: T) => { const renderer = latestRef.current.typeToRenderer[eventType]; From 6d1fd022b341aa26fe2ef956e06d001b5a2571fa Mon Sep 17 00:00:00 2001 From: 7w1 Date: Sat, 11 Jul 2026 23:24:22 -0500 Subject: [PATCH 2/2] ensure more things trigger rerenders --- src/app/features/room/RoomTimeline.tsx | 82 ++++++++++--------- src/app/features/room/ThreadDrawer.tsx | 9 ++ .../hooks/timeline/useProcessedTimeline.ts | 27 +++++- 3 files changed, 79 insertions(+), 39 deletions(-) diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index aefe9843c..73ac90ae8 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -258,7 +258,10 @@ const MemoizedTimelineItem = memo( prev.eventData.willRenderNewDivider === next.eventData.willRenderNewDivider && prev.eventData.willRenderDayDivider === next.eventData.willRenderDayDivider && prev.eventData.mEvent === next.eventData.mEvent && - prev.eventData.eventSender === next.eventData.eventSender + prev.eventData.eventSender === next.eventData.eventSender && + prev.eventData.editId === next.eventData.editId && + prev.eventData.reactionsKey === next.eventData.reactionsKey && + prev.eventData.content === next.eventData.content ); } ); @@ -325,43 +328,46 @@ export function RoomTimeline({ return myPowerLevel < sendLevel; }, [powerLevels, mx]); - const settings = useMemo(() => ({ - messageLayout, - messageSpacing, - hideReads, - showDeveloperTools, - hour24Clock, - dateFormatString, - mediaAutoLoad, - showBundledPreview, - showUrlPreview, - showClientUrlPreview, - showMaps, - autoplayStickers, - hideMemberInReadOnly, - isReadOnly, - hideMembershipEvents, - hideNickAvatarEvents, - hiddenEvents, - }), [ - messageLayout, - messageSpacing, - hideReads, - showDeveloperTools, - hour24Clock, - dateFormatString, - mediaAutoLoad, - showBundledPreview, - showUrlPreview, - showClientUrlPreview, - showMaps, - autoplayStickers, - hideMemberInReadOnly, - isReadOnly, - hideMembershipEvents, - hideNickAvatarEvents, - hiddenEvents, - ]); + const settings = useMemo( + () => ({ + messageLayout, + messageSpacing, + hideReads, + showDeveloperTools, + hour24Clock, + dateFormatString, + mediaAutoLoad, + showBundledPreview, + showUrlPreview, + showClientUrlPreview, + showMaps, + autoplayStickers, + hideMemberInReadOnly, + isReadOnly, + hideMembershipEvents, + hideNickAvatarEvents, + hiddenEvents, + }), + [ + messageLayout, + messageSpacing, + hideReads, + showDeveloperTools, + hour24Clock, + dateFormatString, + mediaAutoLoad, + showBundledPreview, + showUrlPreview, + showClientUrlPreview, + showMaps, + autoplayStickers, + hideMemberInReadOnly, + isReadOnly, + hideMembershipEvents, + hideNickAvatarEvents, + hiddenEvents, + ] + ); const nicknames = useAtomValue(nicknamesAtom); const globalProfiles = useAtomValue(profilesCacheAtom); diff --git a/src/app/features/room/ThreadDrawer.tsx b/src/app/features/room/ThreadDrawer.tsx index 5f680cce5..fdf59c221 100644 --- a/src/app/features/room/ThreadDrawer.tsx +++ b/src/app/features/room/ThreadDrawer.tsx @@ -32,6 +32,8 @@ import { reactionOrEditEvent, resolveReplyDraftTarget, unwrapRelationJumpTarget, + getEditedEvent, + getEventReactions, } from '$utils/room'; import { getMxIdLocalPart, toggleReaction } from '$utils/matrix'; import { useMatrixClient } from '$hooks/useMatrixClient'; @@ -287,6 +289,13 @@ export function ThreadDrawer({ room, threadRootId, onClose, overlay }: ThreadDra collapsed: false, willRenderNewDivider: false, willRenderDayDivider: false, + editId: getEditedEvent(ev.getId() ?? '', ev, timelineSet)?.getId(), + reactionsKey: + getEventReactions(timelineSet, ev.getId() ?? '') + ?.getSortedAnnotationsByKey() + ?.map((r) => `${r[0]}:${r[1].size}`) + .join(',') ?? '', + content: ev.getContent(), })); // forceUpdateCounter makes this recompute whenever events arrive }, [room, threadRootId, thread, processedEvents, forceUpdateCounter]); diff --git a/src/app/hooks/timeline/useProcessedTimeline.ts b/src/app/hooks/timeline/useProcessedTimeline.ts index 45545867e..c4e3f9868 100644 --- a/src/app/hooks/timeline/useProcessedTimeline.ts +++ b/src/app/hooks/timeline/useProcessedTimeline.ts @@ -16,6 +16,8 @@ import { getRedactionTargetEvent, collectRelationReactionEvents, collectRelationEditEvents, + getEditedEvent, + getEventReactions, } from '$utils/room'; import { inSameDay, minuteDifference } from '$utils/time'; import type { ResolvedHiddenEventSettings } from '$state/hooks/settings'; @@ -48,6 +50,9 @@ export interface ProcessedEvent { collapsed: boolean; willRenderNewDivider: boolean; willRenderDayDivider: boolean; + editId: string | undefined; + reactionsKey: string; + content: unknown; } /** Raw timeline indices for skipped events (reactions, edits, …) have no row; walk backward to a visible one. */ @@ -81,7 +86,12 @@ const getPmpId = (ev: MatrixEvent): string | null => type ProcessedEventDraft = Omit< ProcessedEvent, - 'collapsed' | 'willRenderNewDivider' | 'willRenderDayDivider' + | 'collapsed' + | 'willRenderNewDivider' + | 'willRenderDayDivider' + | 'editId' + | 'reactionsKey' + | 'content' >; const computeCollapseAndDividers = ( @@ -137,11 +147,19 @@ const computeCollapseAndDividers = ( if (willRenderNewDivider) newDivider = false; if (willRenderDayDivider) dayDivider = false; + const editId = getEditedEvent(draft.id, mEvent, draft.timelineSet)?.getId(); + const reactions = getEventReactions(draft.timelineSet, draft.id)?.getSortedAnnotationsByKey(); + const reactionsKey = reactions ? reactions.map((r) => `${r[0]}:${r[1].size}`).join(',') : ''; + const content = mEvent.getContent(); + return { ...draft, collapsed, willRenderNewDivider, willRenderDayDivider, + editId, + reactionsKey, + content, }; }); }; @@ -492,6 +510,13 @@ export function useProcessedTimeline({ collapsed, willRenderNewDivider, willRenderDayDivider, + editId: getEditedEvent(mEventId, mEvent, timelineSet)?.getId(), + reactionsKey: + getEventReactions(timelineSet, mEventId) + ?.getSortedAnnotationsByKey() + ?.map((r) => `${r[0]}:${r[1].size}`) + .join(',') ?? '', + content: mEvent.getContent(), }; prevEvent = mEvent;