Fix task conversation scroll reset

This commit is contained in:
klopez4212
2026-06-30 13:55:19 +01:00
parent 0c5ca1f565
commit 14959482b2
5 changed files with 19 additions and 17 deletions
@@ -811,10 +811,10 @@ export function AgentConversationScreen({
emptyTitle="No conversation messages yet"
hasComposerOverlay
isLoading={messagesQuery.isLoading && timelineMessages.length === 0}
layoutShiftKey={conversation.id}
messageListPlacement="top"
messages={timelineMessages}
profiles={profiles}
scrollResetKey={conversation.id}
scrollContainerRef={timelineScrollRef}
showInitialDayDivider={false}
trailingContent={
@@ -794,7 +794,6 @@ export const ChannelPane = React.memo(function ChannelPane({
hasComposerOverlay={hasMainComposerOverlay}
hasOlderMessages={hasOlderMessages}
isFetchingOlder={isFetchingOlder}
layoutShiftKey={useSplitAuxiliaryPane ? "split" : "overlay"}
isFollowingThreadById={isFollowingThreadById}
isMessageUnreadById={isMessageUnreadById}
personaLookup={personaLookup}
@@ -56,7 +56,7 @@ type MessageTimelineProps = {
hasComposerOverlay?: boolean;
contentTopPadding?: "chrome" | "compact";
isFetchingOlder?: boolean;
layoutShiftKey?: string | number | null;
scrollResetKey?: string | number | null;
messageListPlacement?: "bottom" | "top";
messageFooters?: Record<string, React.ReactNode>;
/** Map from lowercase pubkey → persona display name for bot members. */
@@ -164,7 +164,7 @@ const MessageTimelineBase = React.forwardRef<
contentTopPadding = "chrome",
hasOlderMessages = true,
isFetchingOlder = false,
layoutShiftKey = null,
scrollResetKey = null,
messageListPlacement = "bottom",
followThreadById,
huddleMemberPubkeys,
@@ -233,16 +233,15 @@ const MessageTimelineBase = React.forwardRef<
liveSnapshot,
});
const isRenderPending = deferredSnapshot !== liveSnapshot;
const scrollRouteKey = `${channelId ?? "none"}:${layoutShiftKey ?? "none"}`;
const scrollIdentityKey = `${channelId ?? "none"}:${scrollResetKey ?? "none"}`;
const scrollRestorationId = targetMessageId
? `message-timeline:${scrollRouteKey}:target:${targetMessageId}`
: `message-timeline:${scrollRouteKey}`;
// 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 = scrollRouteKey;
? `message-timeline:${scrollIdentityKey}:target:${targetMessageId}`
: `message-timeline:${scrollIdentityKey}`;
// Keep the scroll node's DOM lifetime scoped to the rendered conversation.
// Channel layout changes (for example opening the thread panel) should keep
// this stable, while switching task conversations should start from a fresh
// scroll state.
const scrollContainerDomKey = scrollIdentityKey;
const timelineBodySurface = selectTimelineBodySurface({
deferredCount: deferredMessages.length,
@@ -265,7 +264,7 @@ const MessageTimelineBase = React.forwardRef<
isLoading: showTimelineSkeleton,
messages: deferredMessages,
onTargetReached,
resetKey: scrollRouteKey,
resetKey: scrollIdentityKey,
scrollContainerRef,
targetMessageId,
});
@@ -38,10 +38,10 @@ type UseAnchoredScrollOptions = {
/** Inner content element — must wrap every renderable row, including the
* sentinel and bottom anchor. Used to schedule layout work on resize. */
contentRef: React.RefObject<HTMLDivElement | null>;
/** Resets when changed; lets us drop anchor + scroll state across channels. */
/** Scopes channel-level side effects and observer subscriptions. */
channelId?: string | null;
/** Resets when changed; includes channel plus route-specific layout state. */
resetKey?: string | null;
resetKey?: string | number | null;
/** Suppresses initial scroll-to-bottom while a skeleton is showing. */
isLoading: boolean;
/** Source of truth for the rendered list. Used to detect new-at-bottom
+5 -1
View File
@@ -42,10 +42,14 @@ async function ensureTimelineScrollable(
) {
const input = senderPage.getByTestId("message-input");
const sendButton = senderPage.getByTestId("send-message");
const minimumSeedMessages = 8;
for (let index = 0; index < 24; index += 1) {
const metrics = await getTimelineMetrics(receiverPage);
if (metrics.scrollHeight > metrics.clientHeight + 160) {
if (
index >= minimumSeedMessages &&
metrics.scrollHeight > metrics.clientHeight + 160
) {
return;
}