Load complete chat history when reaching the top (#2481)

* fix(chat): load older timeline history consistently

Pagination depended on scroll events, so short or compacted initial pages could never request older history. Reevaluate edge visibility as layout and history change, and standardize loading indicators on the canonical app spinner.

* fix(chat): keep older history loading reliably

Use authoritative timeline cursor progress so merged rows cannot stall pagination. Wait for bottom anchoring before evaluating the history edge, and re-arm retries on a fresh upward gesture without automatic failure loops.
This commit is contained in:
Mohamed Boudra
2026-07-27 18:21:07 +08:00
committed by GitHub
parent 80c8a08393
commit b97d6d13f3
38 changed files with 609 additions and 177 deletions
+8 -4
View File
@@ -1,3 +1,4 @@
import { LoadingSpinner } from "@/components/ui/loading-spinner";
import React, {
forwardRef,
memo,
@@ -17,7 +18,6 @@ import {
Text,
Pressable,
Platform,
ActivityIndicator,
type PressableStateCallbackType,
type StyleProp,
type ViewStyle,
@@ -247,6 +247,7 @@ export interface AgentStreamViewProps {
historyPagination?: {
hasOlder: boolean;
isLoadingOlder: boolean;
progressKey: string | null;
onLoadOlder: () => void;
};
}
@@ -388,10 +389,11 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
agentId,
toast,
});
const { isLoadingOlder, hasOlder, loadOlder } = historyPagination
const { isLoadingOlder, hasOlder, progressKey, loadOlder } = historyPagination
? {
isLoadingOlder: historyPagination.isLoadingOlder,
hasOlder: historyPagination.hasOlder,
progressKey: historyPagination.progressKey,
loadOlder: historyPagination.onLoadOlder,
}
: agentHistoryPagination;
@@ -1029,6 +1031,7 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
onNearHistoryStart: loadOlder,
isLoadingOlderHistory: isLoadingOlder,
hasOlderHistory: hasOlder,
olderHistoryProgressKey: progressKey,
scrollEnabled: streamScrollEnabled,
listStyle: stylesheet.list,
baseListContentContainerStyle: stylesheet.listContentContainer,
@@ -1138,6 +1141,7 @@ function historyPaginationPropsEqual(
return (
left?.hasOlder === right?.hasOlder &&
left?.isLoadingOlder === right?.isLoadingOlder &&
left?.progressKey === right?.progressKey &&
left?.onLoadOlder === right?.onLoadOlder
);
}
@@ -1194,7 +1198,7 @@ function ToolCallSlot({
return <ToolCall {...rest} onInlineDetailsExpandedChange={handleExpandedChange} />;
}
const ThemedActivityIndicator = withUnistyles(ActivityIndicator);
const ThemedLoadingSpinner = withUnistyles(LoadingSpinner);
const ThemedCheckIcon = withUnistyles(Check);
const ThemedXIcon = withUnistyles(X);
@@ -1241,7 +1245,7 @@ function PermissionActionButton({
return (
<Pressable testID={testID} style={pressableStyle} onPress={handlePress} disabled={isResponding}>
{isRespondingAction ? (
<ThemedActivityIndicator size="small" uniProps={colorMapping} />
<ThemedLoadingSpinner size="small" uniProps={colorMapping} />
) : (
<View style={permissionStyles.optionContent}>
<Icon size={14} uniProps={colorMapping} />