mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
📜 fix: Keep the Newest In-Flight Steer in View
Codex review on de9ede2aad. Valid, and a regression from the 35vh cap in
the previous commit: steers append newest-last, so once the stack
overflows it sits scrolled to the OLDEST entry. The steer just submitted
— and its cancel control — lands below the fold and reads as dropped.
The cap traded "composer pushed off-screen" for "newest steer hidden".
Sticks the stack to the bottom, keyed on the newest steer id so it fires
when one is appended rather than on every render.
This commit is contained in:
parent
de9ede2aad
commit
09c93987a8
2 changed files with 44 additions and 1 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { memo, useMemo, useState, useCallback } from 'react';
|
||||
import { memo, useRef, useMemo, useState, useEffect, useCallback } from 'react';
|
||||
import { X, Zap } from 'lucide-react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import type { TFile, TMessage } from 'librechat-data-provider';
|
||||
|
|
@ -156,12 +156,25 @@ const InFlightSteers = memo(function InFlightSteers({
|
|||
const steers = useRecoilValue(store.pendingSteersByConvoId(conversationId));
|
||||
const inFlight = useMemo(() => steers.filter((steer) => steer.status !== 'failed'), [steers]);
|
||||
|
||||
/** Steers append newest-last, so an overflowing stack would sit scrolled to
|
||||
* the oldest — the steer just submitted (and its cancel) would be below the
|
||||
* fold and read as dropped. Keyed on the newest id, not every render. */
|
||||
const listRef = useRef<HTMLDivElement>(null);
|
||||
const newestId = inFlight[inFlight.length - 1]?.steerId;
|
||||
useEffect(() => {
|
||||
const list = listRef.current;
|
||||
if (list != null) {
|
||||
list.scrollTop = list.scrollHeight;
|
||||
}
|
||||
}, [newestId]);
|
||||
|
||||
if (inFlight.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={listRef}
|
||||
role="list"
|
||||
aria-label={localize('com_ui_steer_in_flight')}
|
||||
data-testid="in-flight-steers"
|
||||
|
|
|
|||
|
|
@ -204,6 +204,36 @@ describe('InFlightSteers', () => {
|
|||
expect(screen.getByTestId('steer-markdown')).toHaveAttribute('data-code-execution', 'false');
|
||||
});
|
||||
|
||||
it('keeps the newest steer in view when the capped stack overflows', () => {
|
||||
// jsdom does no layout, so scrollHeight is 0 unless stubbed — without it
|
||||
// the assertion would pass vacuously against a scrollTop of 0.
|
||||
const scrollHeight = jest
|
||||
.spyOn(HTMLElement.prototype, 'scrollHeight', 'get')
|
||||
.mockReturnValue(600);
|
||||
try {
|
||||
const { rerender } = renderSteers([
|
||||
{ steerId: 's1', text: 'first', status: 'pending', createdAt: 1 },
|
||||
]);
|
||||
// A newly submitted steer appends BELOW the existing ones, so a stack
|
||||
// left scrolled to the top would hide it and its cancel control.
|
||||
rerender(
|
||||
<RecoilRoot
|
||||
initializeState={({ set }) => {
|
||||
set(store.pendingSteersByConvoId(CONVO_ID), [
|
||||
{ steerId: 's1', text: 'first', status: 'pending', createdAt: 1 },
|
||||
{ steerId: 's2', text: 'just submitted', status: 'pending', createdAt: 2 },
|
||||
]);
|
||||
}}
|
||||
>
|
||||
<InFlightSteers conversationId={CONVO_ID} />
|
||||
</RecoilRoot>,
|
||||
);
|
||||
expect(screen.getByTestId('in-flight-steers').scrollTop).toBe(600);
|
||||
} finally {
|
||||
scrollHeight.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it('caps the stack so a long steer cannot push the composer off-screen', () => {
|
||||
renderSteers([{ steerId: 's1', text: 'x'.repeat(4000), status: 'pending', createdAt: 1 }]);
|
||||
// A steer runs to 16k chars, and a run takes up to 10 of them.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue