From 42b758258ac0661834b7c7c9261e10895684b954 Mon Sep 17 00:00:00 2001 From: Evie Gauthier Date: Thu, 9 Apr 2026 13:31:06 -0400 Subject: [PATCH 1/3] fix(timeline): re-arm initial-scroll when TimelineReset blanks the view When the app returns from background, retryImmediately() triggers a fresh sliding sync response. If the server includes initial:true for the open room, the SDK emits RoomEvent.TimelineReset, replacing the live timeline with a new empty one. eventsLength drops to 0 but isReady stays true, leaving the message list visible but empty for ~500 ms until events re-arrive. Add a useLayoutEffect that detects eventsLength dropping to 0 while the timeline is already visible (isReady === true). It hides the content (opacity 0) and resets hasInitialScrolledRef so the initial-scroll effect re-arms. When events refill the timeline the existing 80 ms scroll timer fires again and sets isReady back to true, revealing the content. --- src/app/features/room/RoomTimeline.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index 5c61d9dd4..5f3dba47e 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -328,6 +328,17 @@ export function RoomTimeline({ [] ); + // If the timeline was blanked while content was already visible — e.g. a + // TimelineReset fired by mx.retryImmediately() when the app comes back from + // background — hide the timeline (opacity 0) and re-arm the initial-scroll so + // it runs again once events refill the live timeline. + useLayoutEffect(() => { + if (!isReady) return; + if (timelineSync.eventsLength > 0) return; + setIsReady(false); + hasInitialScrolledRef.current = false; + }, [isReady, timelineSync.eventsLength]); + const recalcTopSpacer = useCallback(() => { const v = vListRef.current; if (!v) return; From dc5dbfdf4ffb7cadb9414a7aac0cc25ef186ada7 Mon Sep 17 00:00:00 2001 From: Evie Gauthier Date: Thu, 9 Apr 2026 14:04:59 -0400 Subject: [PATCH 2/3] fix(timeline): show skeleton placeholders while timeline reloads on foreground --- src/app/features/room/RoomTimeline.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index 5f3dba47e..9076d4502 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -651,7 +651,7 @@ export function RoomTimeline({ const showLoadingPlaceholders = timelineSync.eventsLength === 0 && - (timelineSync.canPaginateBack || timelineSync.backwardStatus === 'loading'); + (!isReady || timelineSync.canPaginateBack || timelineSync.backwardStatus === 'loading'); let backPaginationJSX: ReactNode | undefined; if (timelineSync.canPaginateBack || timelineSync.backwardStatus !== 'idle') { @@ -719,7 +719,7 @@ export function RoomTimeline({ const vListItemCount = timelineSync.eventsLength === 0 && - (timelineSync.canPaginateBack || timelineSync.backwardStatus === 'loading') + (!isReady || timelineSync.canPaginateBack || timelineSync.backwardStatus === 'loading') ? 3 : timelineSync.eventsLength; const vListIndices = useMemo( @@ -851,7 +851,7 @@ export function RoomTimeline({ minHeight: 0, overflow: 'hidden', position: 'relative', - opacity: isReady ? 1 : 0, + opacity: isReady || showLoadingPlaceholders ? 1 : 0, }} > From 3aacd916db36238036c2ffca5149f1360200286f Mon Sep 17 00:00:00 2001 From: Evie Gauthier Date: Thu, 9 Apr 2026 15:13:11 -0400 Subject: [PATCH 3/3] chore: add changeset for timeline-blank-on-foreground fix --- .changeset/fix-timeline-blank-on-foreground.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-timeline-blank-on-foreground.md diff --git a/.changeset/fix-timeline-blank-on-foreground.md b/.changeset/fix-timeline-blank-on-foreground.md new file mode 100644 index 000000000..a214a478f --- /dev/null +++ b/.changeset/fix-timeline-blank-on-foreground.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Fix blank room timeline when app returns from background. When sliding sync delivers an `initial: true` response for the open room, a `TimelineReset` event now correctly shows skeleton placeholders while events reload instead of leaving an empty view.