fix(bug,perf): poll first-vote race, stale timeline ref, lazy GifPicker/EmojiBoard, focusItem timer leak, RoomNavItem memo

BUG-18: clearTimeout cleanup in focusItem useLayoutEffect prevents leaked timers
BUG-24: Room timeline listener catches first poll vote before Relations object exists
BUG-25: Use timelineRef.current in handleOpenEvent to prevent stale index on rapid navigation
Perf-6: React.lazy + Suspense for GifPicker and EmojiBoard (initial bundle -114 kB)
Perf-7: React.memo on RoomNavItem to prevent re-renders on unrelated state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lotus Bot
2026-05-20 21:39:35 -04:00
parent 71791e46f6
commit ee7eabd2c4
4 changed files with 32 additions and 10 deletions
+6 -3
View File
@@ -543,6 +543,8 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
const [timeline, setTimeline] = useState<Timeline>(() =>
eventId ? getEmptyTimeline() : getInitialTimeline(room)
);
const timelineRef = React.useRef(timeline);
timelineRef.current = timeline;
const eventsLength = getTimelinesEventsCount(timeline.linkedTimelines);
const liveTimelineLinked =
timeline.linkedTimelines[timeline.linkedTimelines.length - 1] === getLiveTimeline(room);
@@ -659,7 +661,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
) => {
const evtTimeline = getEventTimeline(room, evtId);
const absoluteIndex =
evtTimeline && getEventIdAbsoluteIndex(timeline.linkedTimelines, evtTimeline, evtId);
evtTimeline && getEventIdAbsoluteIndex(timelineRef.current.linkedTimelines, evtTimeline, evtId);
if (typeof absoluteIndex === 'number') {
const scrolled = scrollToItem(absoluteIndex, {
@@ -678,7 +680,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
loadEventTimeline(evtId);
}
},
[room, timeline, scrollToItem, loadEventTimeline]
[room, scrollToItem, loadEventTimeline]
);
useLiveTimelineRefresh(
@@ -847,13 +849,14 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
});
}
setTimeout(() => {
const timer = setTimeout(() => {
if (!alive()) return;
setFocusItem((currentItem) => {
if (currentItem === focusItem) return undefined;
return currentItem;
});
}, 2000);
return () => clearTimeout(timer);
}, [alive, focusItem, scrollToItem]);
// scroll to bottom of timeline