From 9bc92efe2cdce4157733dd505c731880372c1211 Mon Sep 17 00:00:00 2001 From: 7w1 Date: Wed, 11 Mar 2026 01:39:20 -0500 Subject: [PATCH 01/11] reverse message render order (bottom -> top instead of top -> bottom) --- src/app/features/room/RoomTimeline.css.ts | 2 +- src/app/features/room/RoomTimeline.tsx | 234 ++++++++++++---------- 2 files changed, 131 insertions(+), 105 deletions(-) diff --git a/src/app/features/room/RoomTimeline.css.ts b/src/app/features/room/RoomTimeline.css.ts index 29f18f3a5..5561f7016 100644 --- a/src/app/features/room/RoomTimeline.css.ts +++ b/src/app/features/room/RoomTimeline.css.ts @@ -32,7 +32,7 @@ export type TimelineFloatVariants = RecipeVariants; export const messageList = style({ display: 'flex', - flexDirection: 'column', + flexDirection: 'column-reverse', width: '100%', }); diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index 668b33f7b..ffce24b38 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -2039,101 +2039,74 @@ export function RoomTimeline({ } ); - let prevEvent: MatrixEvent | undefined; - let isPrevRendered = false; - let newDivider = false; - let dayDivider = false; - const timelineItems = getItems(); - const eventRenderer = (item: number) => { - const [eventTimeline, baseIndex] = getTimelineAndBaseIndex(timeline.linkedTimelines, item); - if (!eventTimeline) return null; - const timelineSet = eventTimeline?.getTimelineSet(); - const mEvent = getTimelineEvent(eventTimeline, getTimelineRelativeIndex(item, baseIndex)); - const mEventId = mEvent?.getId(); - - if (!mEvent || !mEventId) return null; - - const eventSender = mEvent.getSender(); - if (eventSender && ignoredUsersSet.has(eventSender)) { - return null; - } - if (mEvent.isRedacted() && !showHiddenEvents) { - return null; - } + const processedEvents = useMemo(() => { + const items = getItems(); + let prevEvent: MatrixEvent | undefined; + let isPrevRendered = false; + let newDivider = false; + let dayDivider = false; + + const chronologicallyProcessed = items + .map((item) => { + const [eventTimeline, baseIndex] = getTimelineAndBaseIndex(timeline.linkedTimelines, item); + if (!eventTimeline) return null; + + const timelineSet = eventTimeline.getTimelineSet(); + const mEvent = getTimelineEvent(eventTimeline, getTimelineRelativeIndex(item, baseIndex)); + const mEventId = mEvent?.getId(); + + if (!mEvent || !mEventId) return null; + + const eventSender = mEvent.getSender(); + if (eventSender && ignoredUsersSet.has(eventSender)) return null; + if (mEvent.isRedacted() && !showHiddenEvents) return null; + + if (!newDivider && readUptoEventIdRef.current) { + newDivider = prevEvent?.getId() === readUptoEventIdRef.current; + } + if (!dayDivider) { + dayDivider = prevEvent ? !inSameDay(prevEvent.getTs(), mEvent.getTs()) : false; + } - if (!newDivider && readUptoEventIdRef.current) { - newDivider = prevEvent?.getId() === readUptoEventIdRef.current; - } - if (!dayDivider) { - dayDivider = prevEvent ? !inSameDay(prevEvent.getTs(), mEvent.getTs()) : false; - } + const isReactionOrEdit = reactionOrEditEvent(mEvent); + const willBeRendered = !isReactionOrEdit; - const collapsed = - isPrevRendered && - !dayDivider && - (!newDivider || eventSender === mx.getUserId()) && - prevEvent !== undefined && - prevEvent.getSender() === eventSender && - prevEvent.getType() === mEvent.getType() && - minuteDifference(prevEvent.getTs(), mEvent.getTs()) < 2; - - const eventJSX = reactionOrEditEvent(mEvent) - ? null - : renderMatrixEvent( - mEvent.getType(), - typeof mEvent.getStateKey() === 'string', - mEventId, - mEvent, - item, - timelineSet, - collapsed - ); - prevEvent = mEvent; - isPrevRendered = !!eventJSX; - - const newDividerJSX = - newDivider && eventJSX && eventSender !== mx.getUserId() ? ( - - - - New Messages - - - - ) : null; - - const dayDividerJSX = - dayDivider && eventJSX ? ( - - - - - {(() => { - if (today(mEvent.getTs())) return 'Today'; - if (yesterday(mEvent.getTs())) return 'Yesterday'; - return timeDayMonthYear(mEvent.getTs()); - })()} - - - - - ) : null; - - if (eventJSX && (newDividerJSX || dayDividerJSX)) { - if (newDividerJSX) newDivider = false; - if (dayDividerJSX) dayDivider = false; + const collapsed = + isPrevRendered && + !dayDivider && + (!newDivider || eventSender === mx.getUserId()) && + prevEvent !== undefined && + prevEvent.getSender() === eventSender && + prevEvent.getType() === mEvent.getType() && + minuteDifference(prevEvent.getTs(), mEvent.getTs()) < 2; - return ( - - {newDividerJSX} - {dayDividerJSX} - {eventJSX} - - ); - } + const willRenderNewDivider = newDivider && willBeRendered && eventSender !== mx.getUserId(); + const willRenderDayDivider = dayDivider && willBeRendered; - return eventJSX; - }; + prevEvent = mEvent; + isPrevRendered = willBeRendered; + + if (willRenderNewDivider) newDivider = false; + if (willRenderDayDivider) dayDivider = false; + + if (!willBeRendered) return null; + + return { + id: mEventId, + itemIndex: item, + mEvent, + timelineSet, + eventSender, + collapsed, + willRenderNewDivider, + willRenderDayDivider, + }; + }) + .filter((e): e is NonNullable => e !== null); + + // Reverse for column-reverse rendering + return chronologicallyProcessed.reverse(); + }, [timeline.linkedTimelines, getItems, ignoredUsersSet, showHiddenEvents, mx]); let backPaginationJSX: ReactNode | undefined; if (canPaginateBack || !rangeAtStart || backwardStatus !== 'idle') { @@ -2158,13 +2131,13 @@ export function RoomTimeline({ ); - } else if (backwardStatus === 'loading' && timelineItems.length > 0) { + } else if (backwardStatus === 'loading' && getItems().length > 0) { backPaginationJSX = ( ); - } else if (timelineItems.length === 0) { + } else if (getItems().length === 0) { // When eventsLength===0 AND liveTimelineLinked the live EventTimeline was // just reset by a sliding sync TimelineRefresh and new events haven't // arrived yet. Attaching the IntersectionObserver anchor here would @@ -2238,13 +2211,13 @@ export function RoomTimeline({ ); - } else if (forwardStatus === 'loading' && timelineItems.length > 0) { + } else if (forwardStatus === 'loading' && getItems().length > 0) { frontPaginationJSX = ( ); - } else if (timelineItems.length === 0) { + } else if (getItems().length === 0) { frontPaginationJSX = messageLayout === MessageLayout.Compact ? ( <> @@ -2310,11 +2283,70 @@ export function RoomTimeline({ - {!canPaginateBack && rangeAtStart && getItems().length > 0 && ( + + {frontPaginationJSX} + + {processedEvents.map((eventData) => { + const { + id, + itemIndex, + mEvent, + timelineSet, + willRenderNewDivider, + willRenderDayDivider, + collapsed, + } = eventData; + + const eventJSX = renderMatrixEvent( + mEvent.getType(), + typeof mEvent.getStateKey() === 'string', + id, + mEvent, + itemIndex, + timelineSet, + collapsed + ); + + const newDividerJSX = willRenderNewDivider ? ( + + + + New Messages + + + + ) : null; + + const dayDividerJSX = willRenderDayDivider ? ( + + + + + {(() => { + if (today(mEvent.getTs())) return 'Today'; + if (yesterday(mEvent.getTs())) return 'Yesterday'; + return timeDayMonthYear(mEvent.getTs()); + })()} + + + + + ) : null; + + return ( + + {eventJSX} + {dayDividerJSX} + {newDividerJSX} + + ); + })} + + {backPaginationJSX} + + {!canPaginateBack && rangeAtStart && processedEvents.length > 0 && (
)} - {backPaginationJSX} - - {timelineItems.map(eventRenderer)} - - {frontPaginationJSX} -
{(!atBottom || !(liveTimelineLinked && rangeAtEnd)) && ( From 921b0d6e9beaf9ca4e69186665973c9b4f862f09 Mon Sep 17 00:00:00 2001 From: 7w1 Date: Wed, 11 Mar 2026 01:43:43 -0500 Subject: [PATCH 02/11] fix context menu not being hoverable --- src/app/features/room/RoomTimeline.css.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/app/features/room/RoomTimeline.css.ts b/src/app/features/room/RoomTimeline.css.ts index 5561f7016..b3037070f 100644 --- a/src/app/features/room/RoomTimeline.css.ts +++ b/src/app/features/room/RoomTimeline.css.ts @@ -38,8 +38,14 @@ export const messageList = style({ globalStyle(`body ${messageList} [data-message-id]`, { transition: 'background-color 0.1s ease-in-out !important', + position: 'relative', + zIndex: 1, }); -globalStyle(`body ${messageList} [data-message-id]:hover`, { - backgroundColor: 'var(--sable-surface-container-hover) !important', -}); +globalStyle( + `body ${messageList} [data-message-id]:hover, body ${messageList} [data-message-id]:focus-within`, + { + backgroundColor: 'var(--sable-surface-container-hover) !important', + zIndex: 10, + } +); From 071453493890696c9ad07841e5bd6acc0513ad24 Mon Sep 17 00:00:00 2001 From: 7w1 Date: Wed, 11 Mar 2026 01:51:11 -0500 Subject: [PATCH 03/11] stabilize pagination hopefully --- src/app/features/room/RoomTimeline.tsx | 64 +++++++++++++------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index ffce24b38..c723881f4 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -345,9 +345,10 @@ const useTimelinePagination = ( const [backwardStatus, setBackwardStatus] = useState('idle'); const [forwardStatus, setForwardStatus] = useState('idle'); - const paginate = useMemo(() => { - let fetching = false; + // Strict lock so timeline no do shift shift + const fetchingRef = useRef({ backward: false, forward: false }); + const paginate = useMemo(() => { const recalibratePagination = ( linkedTimelines: EventTimeline[], timelinesEventsCount: number[], @@ -377,7 +378,11 @@ const useTimelinePagination = ( }; return async (backwards: boolean) => { - if (fetching) return; + const directionKey = backwards ? 'backward' : 'forward'; + + // Enforce the lock + if (fetchingRef.current[directionKey]) return; + const { linkedTimelines: lTimelines } = timelineRef.current; const timelinesEventsCount = lTimelines.map(timelineToEventsCount); @@ -396,7 +401,8 @@ const useTimelinePagination = ( return; } - fetching = true; + // Engage the lock + fetchingRef.current[directionKey] = true; if (alive()) { (backwards ? setBackwardStatus : setForwardStatus)('loading'); } @@ -430,7 +436,8 @@ const useTimelinePagination = ( (backwards ? setBackwardStatus : setForwardStatus)('idle'); } } finally { - fetching = false; + // Release the lock + fetchingRef.current[directionKey] = false; } }; }, [mx, alive, setTimeline, limit, setBackwardStatus, setForwardStatus]); @@ -980,7 +987,7 @@ export function RoomTimeline({ useCallback( () => ({ root: getScrollElement(), - rootMargin: '100px', + rootMargin: '150px 0px 150px 0px', }), [getScrollElement] ), @@ -2131,24 +2138,7 @@ export function RoomTimeline({ ); - } else if (backwardStatus === 'loading' && getItems().length > 0) { - backPaginationJSX = ( - - - - ); } else if (getItems().length === 0) { - // When eventsLength===0 AND liveTimelineLinked the live EventTimeline was - // just reset by a sliding sync TimelineRefresh and new events haven't - // arrived yet. Attaching the IntersectionObserver anchor here would - // immediately fire a server-side /messages request before current events - // land — potentially causing a "/messages hangs → spinner stuck" scenario. - // Suppressing the anchor for this transient state is safe: the rangeAtEnd - // self-heal useEffect will call getInitialTimeline once events arrive, and - // at that point the correct anchor (below) will be re-observed. - // eventsLength>0 covers the range={K,K} case from recalibratePagination - // where items=0 but events exist — that needs the anchor for local range - // extension (no server call since start>0). const placeholderBackAnchor = eventsLength > 0 || !liveTimelineLinked ? observeBackAnchor : undefined; backPaginationJSX = @@ -2184,7 +2174,16 @@ export function RoomTimeline({ ); } else { - backPaginationJSX =
; + backPaginationJSX = ( + <> + {backwardStatus === 'loading' && ( + + + + )} +
+ + ); } } @@ -2211,12 +2210,6 @@ export function RoomTimeline({ ); - } else if (forwardStatus === 'loading' && getItems().length > 0) { - frontPaginationJSX = ( - - - - ); } else if (getItems().length === 0) { frontPaginationJSX = messageLayout === MessageLayout.Compact ? ( @@ -2251,7 +2244,16 @@ export function RoomTimeline({ ); } else { - frontPaginationJSX =
; + frontPaginationJSX = ( + <> +
+ {forwardStatus === 'loading' && ( + + + + )} + + ); } } From 2528f83f1dac14a95d53b5ba8b35fea3df7921fc Mon Sep 17 00:00:00 2001 From: 7w1 Date: Wed, 11 Mar 2026 01:54:33 -0500 Subject: [PATCH 04/11] fix a possible memory leak :p --- src/app/features/room/RoomTimeline.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index c723881f4..d0c2e6916 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -1102,13 +1102,15 @@ export function RoomTimeline({ }); } - setTimeout(() => { + const timeoutId = setTimeout(() => { if (!alive()) return; setFocusItem((currentItem) => { if (currentItem === focusItem) return undefined; return currentItem; }); }, 2000); + + return () => clearTimeout(timeoutId); }, [alive, focusItem, scrollToItem]); // scroll to bottom of timeline From 6fc18296133ca08bb8920010fbcd2f62fb82f09d Mon Sep 17 00:00:00 2001 From: 7w1 Date: Wed, 11 Mar 2026 02:01:08 -0500 Subject: [PATCH 05/11] make the listeners listen better by not destroying themselves every frame i think smh --- src/app/features/room/RoomTimeline.tsx | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index d0c2e6916..66b1f0cd6 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -627,6 +627,12 @@ export function RoomTimeline({ readUptoEventIdRef.current = unreadInfo.readUptoEventId; } + const hideReadsRef = useRef(hideReads); + hideReadsRef.current = hideReads; + + const unreadInfoRef = useRef(unreadInfo); + unreadInfoRef.current = unreadInfo; + const atBottomAnchorRef = useRef(null); const [atBottom, setAtBottomState] = useState(true); @@ -779,14 +785,15 @@ export function RoomTimeline({ // otherwise we update timeline without paginating // so timeline can be updated with evt like: edits, reactions etc if (atBottomRef.current && atLiveEndRef.current) { - if (document.hasFocus() && (!unreadInfo || mEvt.getSender() === mx.getUserId())) { - // Check if the document is in focus (user is actively viewing the app), - // and either there are no unread messages or the latest message is from the current user. - // If either condition is met, trigger the markAsRead function to send a read receipt. - requestAnimationFrame(() => markAsRead(mx, mEvt.getRoomId()!, hideReads)); + if ( + document.hasFocus() && + (!unreadInfoRef.current || mEvt.getSender() === mx.getUserId()) + ) { + // Check if the document is in focus and trigger markAsRead + requestAnimationFrame(() => markAsRead(mx, mEvt.getRoomId()!, hideReadsRef.current)); } - if (!document.hasFocus() && !unreadInfo) { + if (!document.hasFocus() && !unreadInfoRef.current) { setUnreadInfo(getRoomUnreadInfo(room)); } @@ -805,11 +812,11 @@ export function RoomTimeline({ return; } setTimeline((ct) => ({ ...ct })); - if (!unreadInfo) { + if (!unreadInfoRef.current) { setUnreadInfo(getRoomUnreadInfo(room)); } }, - [mx, room, unreadInfo, hideReads] + [mx, room, setUnreadInfo] ) ); From 9810a9984013df6ec20c5bab9c3b024e6a49ecca Mon Sep 17 00:00:00 2001 From: 7w1 Date: Wed, 11 Mar 2026 02:13:19 -0500 Subject: [PATCH 06/11] plz work mobile scroll thanks --- src/app/features/room/RoomTimeline.css.ts | 2 ++ src/app/features/room/RoomTimeline.tsx | 11 ++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/app/features/room/RoomTimeline.css.ts b/src/app/features/room/RoomTimeline.css.ts index b3037070f..8db3a10ae 100644 --- a/src/app/features/room/RoomTimeline.css.ts +++ b/src/app/features/room/RoomTimeline.css.ts @@ -34,9 +34,11 @@ export const messageList = style({ display: 'flex', flexDirection: 'column-reverse', width: '100%', + overflowAnchor: 'none', }); globalStyle(`body ${messageList} [data-message-id]`, { + overflowAnchor: 'auto', transition: 'background-color 0.1s ease-in-out !important', position: 'relative', zIndex: 1, diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index 66b1f0cd6..661b225a5 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -2184,14 +2184,19 @@ export function RoomTimeline({ ); } else { backPaginationJSX = ( - <> +
{backwardStatus === 'loading' && ( )} -
- +
); } } From 6fc2aecd22e062962fb5a3a0542c8e33bc56fa4b Mon Sep 17 00:00:00 2001 From: 7w1 Date: Wed, 11 Mar 2026 02:23:12 -0500 Subject: [PATCH 07/11] plz work attempt 2 --- src/app/features/room/RoomTimeline.tsx | 39 ++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index 661b225a5..2d680610d 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -2271,6 +2271,45 @@ export function RoomTimeline({ } } + const prevBackwardStatus = useRef(backwardStatus); + const scrollAnchor = useRef<{ id: string; offsetTop: number } | null>(null); + + useLayoutEffect(() => { + const scrollEl = scrollRef.current; + if (!scrollEl) return; + + // When going to loading store the exact position + if (prevBackwardStatus.current === 'idle' && backwardStatus === 'loading') { + const topMessage = scrollEl.querySelector('[data-message-id]'); + if (topMessage) { + scrollAnchor.current = { + id: topMessage.getAttribute('data-message-id') || '', + offsetTop: (topMessage as HTMLElement).offsetTop, + }; + } + } + + // After fetch finishes calculate new content and force scroll to position hopefully + if (prevBackwardStatus.current === 'loading' && backwardStatus === 'idle') { + if (scrollAnchor.current) { + const anchorNode = scrollEl.querySelector( + `[data-message-id="${scrollAnchor.current.id}"]` + ) as HTMLElement; + + if (anchorNode) { + const delta = anchorNode.offsetTop - scrollAnchor.current.offsetTop; + + if (delta > 0) { + scrollEl.scrollBy({ top: delta, behavior: 'instant' }); + } + } + scrollAnchor.current = null; + } + } + + prevBackwardStatus.current = backwardStatus; + }, [backwardStatus, processedEvents]); + return ( {unreadInfo?.readUptoEventId && !unreadInfo?.inLiveTimeline && ( From 9beff5d4fb0bee4b2bee5f1c973281d16603f3ab Mon Sep 17 00:00:00 2001 From: 7w1 Date: Wed, 11 Mar 2026 02:35:08 -0500 Subject: [PATCH 08/11] plz work part 3 electric boogalee --- src/app/features/room/RoomTimeline.tsx | 57 +++++++++----------------- 1 file changed, 19 insertions(+), 38 deletions(-) diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index 2d680610d..5d7136001 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -2185,18 +2185,18 @@ export function RoomTimeline({ } else { backPaginationJSX = (
- {backwardStatus === 'loading' && ( - - - - )} -
+ /> + ); + const backwardLoadingJSX = backwardStatus === 'loading' && ( + + + ); } } @@ -2270,45 +2270,26 @@ export function RoomTimeline({ ); } } - - const prevBackwardStatus = useRef(backwardStatus); - const scrollAnchor = useRef<{ id: string; offsetTop: number } | null>(null); + const lastScrollHeight = useRef(0); useLayoutEffect(() => { const scrollEl = scrollRef.current; if (!scrollEl) return; - // When going to loading store the exact position - if (prevBackwardStatus.current === 'idle' && backwardStatus === 'loading') { - const topMessage = scrollEl.querySelector('[data-message-id]'); - if (topMessage) { - scrollAnchor.current = { - id: topMessage.getAttribute('data-message-id') || '', - offsetTop: (topMessage as HTMLElement).offsetTop, - }; - } + if (backwardStatus === 'loading') { + lastScrollHeight.current = scrollEl.scrollHeight; } - // After fetch finishes calculate new content and force scroll to position hopefully - if (prevBackwardStatus.current === 'loading' && backwardStatus === 'idle') { - if (scrollAnchor.current) { - const anchorNode = scrollEl.querySelector( - `[data-message-id="${scrollAnchor.current.id}"]` - ) as HTMLElement; - - if (anchorNode) { - const delta = anchorNode.offsetTop - scrollAnchor.current.offsetTop; + if (backwardStatus === 'idle' && lastScrollHeight.current > 0) { + const newScrollHeight = scrollEl.scrollHeight; + const heightDifference = newScrollHeight - lastScrollHeight.current; - if (delta > 0) { - scrollEl.scrollBy({ top: delta, behavior: 'instant' }); - } - } - scrollAnchor.current = null; + if (heightDifference > 0) { + scrollEl.scrollTop += heightDifference; } + lastScrollHeight.current = 0; } - - prevBackwardStatus.current = backwardStatus; - }, [backwardStatus, processedEvents]); + }, [backwardStatus, processedEvents.length]); return ( From b4bff803edba38f17267f5fa956b29fa81c6488a Mon Sep 17 00:00:00 2001 From: 7w1 Date: Wed, 11 Mar 2026 02:40:57 -0500 Subject: [PATCH 09/11] fix thingy --- src/app/features/room/RoomTimeline.tsx | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index 5d7136001..b6f5eed4e 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -2184,18 +2184,20 @@ export function RoomTimeline({ ); } else { backPaginationJSX = ( -
- ); - const backwardLoadingJSX = backwardStatus === 'loading' && ( - - + + {backwardStatus === 'loading' && ( + + + + )} +
); } From 88e9efa95c152056cd5c819eb5bd236a03a20a62 Mon Sep 17 00:00:00 2001 From: 7w1 Date: Wed, 11 Mar 2026 12:32:09 -0500 Subject: [PATCH 10/11] fix hover and maybe scroll idk --- src/app/features/room/RoomTimeline.css.ts | 20 ++++++++++++-------- src/app/features/room/RoomTimeline.tsx | 8 ++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/app/features/room/RoomTimeline.css.ts b/src/app/features/room/RoomTimeline.css.ts index 8db3a10ae..fd60630fb 100644 --- a/src/app/features/room/RoomTimeline.css.ts +++ b/src/app/features/room/RoomTimeline.css.ts @@ -37,17 +37,21 @@ export const messageList = style({ overflowAnchor: 'none', }); -globalStyle(`body ${messageList} [data-message-id]`, { +globalStyle(`body ${messageList} > *`, { overflowAnchor: 'auto', +}); + +globalStyle(`body ${messageList} [data-message-id]`, { transition: 'background-color 0.1s ease-in-out !important', position: 'relative', zIndex: 1, }); -globalStyle( - `body ${messageList} [data-message-id]:hover, body ${messageList} [data-message-id]:focus-within`, - { - backgroundColor: 'var(--sable-surface-container-hover) !important', - zIndex: 10, - } -); +globalStyle(`body ${messageList} [data-message-id]:hover`, { + backgroundColor: 'var(--sable-surface-container-hover) !important', + zIndex: 2, +}); + +globalStyle(`body ${messageList} [data-message-id]:focus-within`, { + zIndex: 10, +}); diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index b6f5eed4e..355bfe362 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -2186,17 +2186,13 @@ export function RoomTimeline({ backPaginationJSX = ( {backwardStatus === 'loading' && ( - + )}
); From fecfe3d02499ae17d7ec9793462ce61789b12154 Mon Sep 17 00:00:00 2001 From: 7w1 Date: Wed, 11 Mar 2026 16:54:03 -0500 Subject: [PATCH 11/11] delete the manual math and hope it works? --- src/app/features/room/RoomTimeline.tsx | 55 ++++++++++++-------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index 355bfe362..dadd2f245 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -2184,16 +2184,22 @@ export function RoomTimeline({ ); } else { backPaginationJSX = ( - + +
{backwardStatus === 'loading' && ( - + )} -
); } @@ -2257,37 +2263,26 @@ export function RoomTimeline({ ); } else { frontPaginationJSX = ( - <> -
+ +
{forwardStatus === 'loading' && ( - + )} - + ); } } - const lastScrollHeight = useRef(0); - - useLayoutEffect(() => { - const scrollEl = scrollRef.current; - if (!scrollEl) return; - - if (backwardStatus === 'loading') { - lastScrollHeight.current = scrollEl.scrollHeight; - } - - if (backwardStatus === 'idle' && lastScrollHeight.current > 0) { - const newScrollHeight = scrollEl.scrollHeight; - const heightDifference = newScrollHeight - lastScrollHeight.current; - - if (heightDifference > 0) { - scrollEl.scrollTop += heightDifference; - } - lastScrollHeight.current = 0; - } - }, [backwardStatus, processedEvents.length]); return (