page older messages in when the folded ones don't fill the screen

Regression from the fold change. Scroll-up paging is driven by a scroll listener, and a scroll listener
only fires on a list that actually scrolls. That was always true and never mattered, because the turn you
were looking at rendered in full and was tall enough on its own.

Folding on reload broke it. A window of twenty messages can be one turn with eighteen tool calls, which
collapses to three short rows: no overflow, no scroll event, and paging never starts. The whole
transcript above becomes unreachable — which reads as lost history rather than as a fetch that never
fired.

So don't wait for a scroll that cannot happen: after each render, if there is more to load and the
content does not overflow its viewport, load the next page. Terminates because each pass either fills the
viewport or exhausts the transcript.

This also fixes a latent case that predates folding — any first window short enough to fit on screen
could never be paged past.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 03:50:34 +01:00
co-authored by Claude Opus 5
parent 7774a25ad9
commit 0210e85762
@@ -122,6 +122,28 @@ export function useEmbeddableChat(params: UseEmbeddableChatParams, onMessageComp
if (textareaRef.current) textareaRef.current.style.height = 'auto';
};
/**
* Page older messages in when the loaded ones do not fill the viewport.
*
* Scroll-up paging is driven by a scroll listener, and a scroll listener only fires on a list that
* actually scrolls. That was always true and never mattered, because the turn you were looking at
* rendered in full and was tall enough on its own. Folding on reload broke it: a window of twenty
* messages can be one turn with eighteen tool calls, which collapses to three short rows — no overflow,
* no scroll event, and paging simply never starts. The symptom is a transcript you cannot scroll back
* through at all, which reads as lost history rather than as a stalled fetch.
*
* Terminates: each pass either fills the viewport or exhausts the transcript, and `hasMoreOlder` goes
* false at the top. It runs after every prepend, so several small pages in a row are fine.
*/
useEffect(() => {
const viewport = scrollViewportRef.current;
if (!viewport || !didInitialScrollRef.current) return;
if (!hasMoreOlder || isLoadingOlder) return;
// A pixel of slack: a list exactly as tall as its viewport cannot be scrolled either.
if (viewport.scrollHeight > viewport.clientHeight + 1) return;
void loadOlder();
}, [messages, hasMoreOlder, isLoadingOlder, loadOlder]);
/**
* Hand a batch to the agent as ONE message. Returns whether it actually started a turn — a slash
* command the client handled itself (`/clear`, `/model`) never reaches the agent, so the drain below