diff --git a/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts b/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts index 3e86d79a..504ff125 100644 --- a/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts +++ b/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts @@ -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