mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
fix(site/src/pages/AgentsPage): fire chat-ready after store sync so scroll-to-bottom hits rendered DOM (#23693)
`onChatReady` now waits for the store to have all fetched messages (not just query success), so the DOM has content when the parent frame acts on the signal. Removed the `scroll-to-bottom` round-trip from `onChatReady`, the embed page no longer needs the parent to echo a scroll command back. Also moved `autoScrollRef` check before the `widthChanged` guard in `ScrollAnchoredContainer`'s ResizeObserver. The width guard is only relevant for scroll compensation (user scrolled up); it should never prevent pinning to bottom when auto-scroll is active. Also tightened the store sync guard to `storeMessageCount < fetchedMessageCount`.
This commit is contained in:
@@ -884,15 +884,28 @@ const AgentDetail: FC = () => {
|
||||
requestUnarchiveAgent(agentId);
|
||||
};
|
||||
|
||||
// Signal the parent layout that messages have loaded.
|
||||
// Signal ready only after the store has synced fetched messages,
|
||||
// so the DOM actually contains them when the parent scrolls.
|
||||
const chatReadyFiredRef = useRef<string | null>(null);
|
||||
const storeMessageCount = useChatSelector(store, (s) => s.messagesByID.size);
|
||||
const fetchedMessageCount = chatMessagesList?.length ?? 0;
|
||||
useEffect(() => {
|
||||
if (chatReadyFiredRef.current === agentId || !chatMessagesQuery.isSuccess) {
|
||||
if (
|
||||
chatReadyFiredRef.current === agentId ||
|
||||
!chatMessagesQuery.isSuccess ||
|
||||
storeMessageCount < fetchedMessageCount
|
||||
) {
|
||||
return;
|
||||
}
|
||||
chatReadyFiredRef.current = agentId ?? null;
|
||||
onChatReady();
|
||||
}, [onChatReady, chatMessagesQuery.isSuccess, agentId]);
|
||||
}, [
|
||||
onChatReady,
|
||||
storeMessageCount,
|
||||
fetchedMessageCount,
|
||||
chatMessagesQuery.isSuccess,
|
||||
agentId,
|
||||
]);
|
||||
|
||||
const handleRegenerateTitle = () => {
|
||||
if (!agentId || isRegenerateTitleDisabled || !onRegenerateTitle) {
|
||||
|
||||
@@ -194,8 +194,7 @@ const AgentEmbedPage: FC = () => {
|
||||
// instead of creating its own.
|
||||
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
// Listen for parent frame commands: theme changes and
|
||||
// scroll-to-bottom requests.
|
||||
// Listen for parent frame commands (e.g. theme changes).
|
||||
useEffect(() => {
|
||||
const parentWindow = window.parent;
|
||||
const handler = (event: MessageEvent) => {
|
||||
@@ -205,14 +204,6 @@ const AgentEmbedPage: FC = () => {
|
||||
const theme = getThemeFromMessage(event.data);
|
||||
if (theme) {
|
||||
applyEmbedTheme(theme);
|
||||
return;
|
||||
}
|
||||
if (event.data?.type === "coder:scroll-to-bottom") {
|
||||
// Normal flow: scroll to the bottom of the transcript.
|
||||
if (scrollContainerRef.current) {
|
||||
scrollContainerRef.current.scrollTop =
|
||||
scrollContainerRef.current.scrollHeight;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -842,6 +842,11 @@ const ScrollAnchoredContainer: FC<{
|
||||
return;
|
||||
}
|
||||
|
||||
if (autoScrollRef.current) {
|
||||
scheduleBottomPin();
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip compensation during reflow. Width changes indicate the
|
||||
// height delta is distributed through the transcript rather than
|
||||
// appended at the bottom, so applying the full delta would
|
||||
@@ -850,11 +855,6 @@ const ScrollAnchoredContainer: FC<{
|
||||
return;
|
||||
}
|
||||
|
||||
if (autoScrollRef.current) {
|
||||
scheduleBottomPin();
|
||||
return;
|
||||
}
|
||||
|
||||
// In normal flow, appends grow below the viewport, so users reading
|
||||
// history do not need scroll compensation.
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user