fix(chat): improve mobile history loading and virtualization fidelity
Give touch surfaces a larger, viewport-relative head start for loading older history so an in-flight fetch completes before the finger reaches the top. Raise the mobile virtualizer overscan so fast flings stay populated instead of leaving blank gaps, and drop the fixed itemSize hint so virtua auto-estimates row heights from measured sizes instead of a flat constant.
This commit is contained in:
@@ -19,25 +19,22 @@ import type { StreamPhase } from './message/types';
|
||||
import { useGlobalSessionsStore } from '@/stores/useGlobalSessionsStore';
|
||||
import { useSessionParts } from '@/sync/sync-context';
|
||||
import type { ReviewTransferDirection } from '@/lib/reviewFlow';
|
||||
import { isMobileSurfaceRuntime } from '@/lib/runtimeSurface';
|
||||
|
||||
const MESSAGE_LIST_VIRTUALIZE_THRESHOLD = 5;
|
||||
const EMPTY_STATIC_ENTRY_MESSAGES: ChatMessageEntry[] = [];
|
||||
const EMPTY_UNGROUPED_MESSAGE_IDS = new Set<string>();
|
||||
const MESSAGE_LIST_BUFFER_SIZE = 900;
|
||||
// Touch surfaces fling-scroll natively and dispatch scroll events less often
|
||||
// than the virtualizer can repaint, so a desktop-sized buffer leaves blank gaps
|
||||
// during momentum that only fill once measurement catches up. A larger overscan
|
||||
// keeps more rows mounted around the viewport so fast flings stay populated.
|
||||
const MOBILE_MESSAGE_LIST_BUFFER_SIZE = 2400;
|
||||
const resolveMessageListBufferSize = (): number => (
|
||||
isMobileSurfaceRuntime() ? MOBILE_MESSAGE_LIST_BUFFER_SIZE : MESSAGE_LIST_BUFFER_SIZE
|
||||
);
|
||||
const TIMELINE_CACHE_LIMIT = 16;
|
||||
|
||||
const estimateHistoryEntryHeight = (entry: RenderEntry | undefined): number => {
|
||||
if (!entry) {
|
||||
return 160;
|
||||
}
|
||||
|
||||
if (entry.kind === 'turn') {
|
||||
return 180 + Math.min(entry.turn.assistantMessages.length, 4) * 100;
|
||||
}
|
||||
|
||||
return 140;
|
||||
};
|
||||
|
||||
const sameKeys = (a: readonly string[] | undefined, b: readonly string[] | undefined): boolean => {
|
||||
if (a === b) return true;
|
||||
if (!a || !b) return false;
|
||||
@@ -982,8 +979,7 @@ const StaticHistoryList = React.memo(({ entries, shouldVirtualize, contentRef, s
|
||||
ref={virtualizerRef}
|
||||
data={entries}
|
||||
cache={virtualCache}
|
||||
itemSize={virtualCache ? undefined : estimateHistoryEntryHeight(undefined)}
|
||||
bufferSize={MESSAGE_LIST_BUFFER_SIZE}
|
||||
bufferSize={resolveMessageListBufferSize()}
|
||||
shift={shift}
|
||||
scrollRef={scrollRef}
|
||||
>
|
||||
|
||||
@@ -60,6 +60,24 @@ export interface UseChatTimelineControllerResult {
|
||||
|
||||
const TURN_MODEL_CACHE_MAX = 30
|
||||
const HISTORY_SCROLL_THRESHOLD = 200
|
||||
// On touch surfaces the user can drag continuously toward the top, and
|
||||
// loadEarlier is an async (network) fetch. A 200px lead is enough on desktop
|
||||
// (wheel + fast render) but the finger can outrun an in-flight fetch on mobile
|
||||
// and hit the very top before history lands. Give touch a much larger,
|
||||
// viewport-relative head start so the fetch completes before the top is
|
||||
// reached, regardless of how fast the user drags.
|
||||
const MOBILE_HISTORY_SCROLL_THRESHOLD_MIN = 1200
|
||||
const MOBILE_HISTORY_SCROLL_VIEWPORT_FACTOR = 2
|
||||
|
||||
const resolveHistoryScrollThreshold = (clientHeight: number): number => {
|
||||
if (!isMobileSurfaceRuntime()) {
|
||||
return HISTORY_SCROLL_THRESHOLD
|
||||
}
|
||||
return Math.max(
|
||||
MOBILE_HISTORY_SCROLL_THRESHOLD_MIN,
|
||||
clientHeight * MOBILE_HISTORY_SCROLL_VIEWPORT_FACTOR,
|
||||
)
|
||||
}
|
||||
const VSCODE_TURN_MODEL_CACHE_MAX = 4
|
||||
const VSCODE_TURN_MODEL_CACHE_MAX_MESSAGES = 30
|
||||
const MOBILE_TURN_MODEL_CACHE_MAX = 4
|
||||
@@ -521,7 +539,7 @@ export const useChatTimelineController = ({
|
||||
const container = scrollRef.current;
|
||||
if (!container) return;
|
||||
if (isPinnedRef.current) return;
|
||||
if (container.scrollTop >= HISTORY_SCROLL_THRESHOLD) return;
|
||||
if (container.scrollTop >= resolveHistoryScrollThreshold(container.clientHeight)) return;
|
||||
if (!historySignalsRef.current.canLoadEarlier) return;
|
||||
if (isLoadingOlderRef.current || pendingRevealWorkRef.current) return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user