From 0c5dbf3452802a0370c1bdb445efa602c98c4148 Mon Sep 17 00:00:00 2001 From: Tyler <109685178+tlongwell-block@users.noreply.github.com> Date: Fri, 19 Jun 2026 15:31:25 -0400 Subject: [PATCH] fix(desktop): remount timeline scroll node per channel (#1147) Signed-off-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co> Co-authored-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co> --- desktop/src/features/channels/ui/ChannelPane.tsx | 2 +- desktop/src/features/messages/ui/MessageTimeline.tsx | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/desktop/src/features/channels/ui/ChannelPane.tsx b/desktop/src/features/channels/ui/ChannelPane.tsx index f75a0cd90b..ba1bda56ec 100644 --- a/desktop/src/features/channels/ui/ChannelPane.tsx +++ b/desktop/src/features/channels/ui/ChannelPane.tsx @@ -264,7 +264,7 @@ export const ChannelPane = React.memo(function ChannelPane({ useComposerHeightPadding( timelineScrollRef, composerWrapperRef, - `${isSinglePanelView}:${hasMainComposerOverlay}`, + `${activeChannelId}:${isSinglePanelView}:${hasMainComposerOverlay}`, ); const clearWelcomeComposerDismissTimer = React.useCallback(() => { diff --git a/desktop/src/features/messages/ui/MessageTimeline.tsx b/desktop/src/features/messages/ui/MessageTimeline.tsx index e53fbdcd50..19a37ddf80 100644 --- a/desktop/src/features/messages/ui/MessageTimeline.tsx +++ b/desktop/src/features/messages/ui/MessageTimeline.tsx @@ -205,6 +205,12 @@ const MessageTimelineBase = React.forwardRef< const scrollRestorationId = targetMessageId ? `message-timeline:${channelId ?? "none"}:target:${targetMessageId}` : `message-timeline:${channelId ?? "none"}`; + // Keep the scroll node's DOM lifetime scoped to a channel. TanStack Router's + // scroll-restoration listener runs outside React and may write a saved + // scrollTop into the current scroll element during navigation; reusing the + // same node across channel routes can leave the newly-loaded message list + // painted at a stale offset until the user's next scroll event forces layout. + const scrollContainerDomKey = channelId ?? "none"; const timelineBodySurface = selectTimelineBodySurface({ deferredCount: deferredMessages.length, @@ -339,6 +345,7 @@ const MessageTimelineBase = React.forwardRef< )} data-scroll-restoration-id={scrollRestorationId} data-testid="message-timeline" + key={scrollContainerDomKey} onScroll={onScroll} ref={scrollContainerRef} >