fix(chat): hide the load-older button once history is confirmed complete

The button's visibility mixed the sync meta with the prefetch-cache hint;
a stale prefetch entry (cursor recorded at the initial page) could keep
the affordance alive after the user had already loaded to the top. Sync
now exposes an explicit isComplete (positive confirmation from a fetch,
distinct from !hasMore on unpopulated meta) and it overrides the prefetch
hint, which stays in effect only before the meta knows anything.
This commit is contained in:
Bohdan Triapitsyn
2026-07-05 01:54:33 +03:00
parent 2c220f3c52
commit 01ca2ecf24
2 changed files with 19 additions and 3 deletions
@@ -522,10 +522,17 @@ export const ChatContainer: React.FC<ChatContainerProps> = ({ autoOpenDraft = tr
// History metadata — use sync's hasMore/isLoading
const historyMeta = React.useMemo(() => {
if (!currentSessionId) return null;
const prefetchHasMore = Boolean(sessionPrefetchInfo?.cursor) && sessionPrefetchInfo?.complete !== true;
// Sync's meta is authoritative once a fetch has confirmed the history
// is fully loaded — a stale prefetch-cache entry (cursor recorded at
// the initial page) must not keep the "load older" affordance alive
// after the user has already reached the top.
const syncComplete = sync.isComplete(currentSessionId);
const prefetchHasMore = !syncComplete
&& Boolean(sessionPrefetchInfo?.cursor)
&& sessionPrefetchInfo?.complete !== true;
return {
limit: sessionMessages.length,
complete: !(sync.hasMore(currentSessionId) || prefetchHasMore),
complete: syncComplete || !(sync.hasMore(currentSessionId) || prefetchHasMore),
loading: sync.isLoading(currentSessionId),
};
}, [currentSessionId, sessionMessages.length, sessionPrefetchInfo, sync]);
+10 -1
View File
@@ -555,6 +555,14 @@ export function useSync() {
[getMetaFor],
)
// True only when a fetch has positively confirmed the history is fully
// loaded (no next cursor). Distinct from !hasMore(), which is also true for
// sessions whose meta simply hasn't been populated yet.
const isComplete = useCallback(
(sessionID: string) => getMetaFor(sessionID).complete,
[getMetaFor],
)
// Optimistic add (for prompt submission)
const optimisticAdd = useCallback(
(input: { sessionID: string; directory?: string | null; message: Message; parts: Part[] }) => {
@@ -610,11 +618,12 @@ export function useSync() {
loadMore,
hasMore,
isLoading,
isComplete,
optimistic: {
add: optimisticAdd,
remove: optimisticRemove,
},
}),
[syncSession, loadMore, hasMore, isLoading, optimisticAdd, optimisticRemove],
[syncSession, loadMore, hasMore, isLoading, isComplete, optimisticAdd, optimisticRemove],
)
}