Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions apps/web/src/components/chat/MessagesTimeline.browser.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,32 @@ describe("MessagesTimeline messages", () => {
expect(turns).toEqual(turns.toSorted((a, b) => a - b));
});

it("keeps reporting overflow while a parent re-renders it", async () => {
setup();
const props = baseProps();
const onContentOverflowChange = vi.fn();
const entries = Array.from({ length: 40 }, (_, index) =>
message(`Turn ${index}:\n\n${"x ".repeat(200)}`, {}, `turn-${index}`),
);
const element = () => (
<MessagesTimeline
{...props}
timelineEntries={entries}
onContentOverflowChange={onContentOverflowChange}
onIsAtEndChange={() => {}}
/>
);
await using screen = await renderElement(element());
await vi.waitFor(() => expect(onContentOverflowChange).toHaveBeenLastCalledWith(true));
onContentOverflowChange.mockClear();

await screen.rerender(element());
await vi.waitFor(() => {
expect(onContentOverflowChange).toHaveBeenLastCalledWith(true);
expect(onContentOverflowChange).not.toHaveBeenCalledWith(false);
});
});

it("preserves XML-like tags and comparisons in user messages as text", async () => {
const source = [
'Without reading a file, do you have <global-agent-instructions scope="workspace">',
Expand Down
12 changes: 10 additions & 2 deletions apps/web/src/components/chat/MessagesTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1144,7 +1144,9 @@ const ConversationTimeline = memo(function ConversationTimeline({

const canvas = useChatCanvas();
const registerTimeline = canvas?.registerTimeline;
const setTimelineList = (list: LegendListRef | null) => {
const scheduleContentSizeChangedRef = useRef<(() => void) | null>(null);
// A new ref callback identity detaches the list, and detaching reports no overflow.
const setTimelineList = useStableCallback((list: LegendListRef | null) => {
listRef.current = list;
const scrollNode: HTMLElement | null = list?.getScrollableNode() ?? null;
registerTimeline?.(scrollNode);
Expand All @@ -1171,8 +1173,10 @@ const ConversationTimeline = memo(function ConversationTimeline({
const stopListeningToContentSize = list
?.getState()
.listen("totalSize", () => scheduleContentSizeChanged());
scheduleContentSizeChangedRef.current = scheduleContentSizeChanged;
scheduleContentSizeChanged();
return () => {
scheduleContentSizeChangedRef.current = null;
stopListeningToContentSize?.();
stopRememberingRelease?.();
onContentOverflowChange?.(false);
Expand All @@ -1190,7 +1194,11 @@ const ConversationTimeline = memo(function ConversationTimeline({
listRef.current = null;
registerTimeline?.(null);
};
};
});
// oxlint-disable-next-line t3code/no-unreasoned-state-sync -- re-measures overflow and thread settling after renders that change rows or the composer inset without resizing the content
useLayoutEffect(() => {
scheduleContentSizeChangedRef.current?.();
});

useThreadFindPositionReader({
threadKey: listIdentityKey,
Expand Down
Loading