fix(chat): Such-Scroll springt nicht mehr endlos (Retry-Limit + Skip)
Symptom: Suche zeigt Treffer, springt aber permanent zwischen Bubbles hin und her in Endlosschleife. Zwei Ursachen, beide angeschlossen: 1) agent_activity-Handler rief setMessages mit prev.map() — auch wenn keine sending-Bubble da war. Das erzeugte trotzdem ein neues Array bei jedem Tool-Event (5-10x pro Brain-Call). invertedMessages neu → FlatList-Layouts invalidiert mitten in einer aktiven Scroll-Sequenz. Fix: prev.some() vor map() — wenn nichts zu aendern ist, prev unveraendert returnen (reference-stable, kein Re-Render). 2) onScrollToIndexFailed retried unbegrenzt. Jeder failed Retry rief den Handler erneut auf → neuer setTimeout → neuer Versuch → fail → loop. Vorher waren cascading 3 Retries, dann auf 1 reduziert um den 3-9-27-Cascade zu fixen, aber EIN ungebremster Retry-Schluss pro fail bleibt eine Endlos-Schleife wenn Layouts nie stabil werden. Fix: harter Counter (MAX_SCROLL_RETRIES = 3). Counter wird bei jedem neuen Search-Hit via clearPendingScrollRetry resettet. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1041,11 +1041,20 @@ const ChatScreen: React.FC = () => {
|
||||
for (const cmid of Array.from(ackTimers.current.keys())) {
|
||||
clearAckTimer(cmid);
|
||||
}
|
||||
setMessages(prev => prev.map(m =>
|
||||
m.sender === 'user' && m.deliveryStatus === 'sending'
|
||||
? { ...m, deliveryStatus: 'sent' }
|
||||
: m
|
||||
));
|
||||
// Reference-stable: wenn keine Bubble zu aendern ist, geben wir
|
||||
// prev unveraendert zurueck. Sonst triggert .map() ein neues
|
||||
// Array + Re-Render, was waehrend einer aktiven Such-Scroll-
|
||||
// Sequenz die FlatList-Layouts invalidiert → permanenter
|
||||
// onScrollToIndexFailed-Loop.
|
||||
setMessages(prev => {
|
||||
const needs = prev.some(m => m.sender === 'user' && m.deliveryStatus === 'sending');
|
||||
if (!needs) return prev;
|
||||
return prev.map(m =>
|
||||
m.sender === 'user' && m.deliveryStatus === 'sending'
|
||||
? { ...m, deliveryStatus: 'sent' }
|
||||
: m,
|
||||
);
|
||||
});
|
||||
}
|
||||
// In den Gedanken-Stream einfuegen. Dedup gegen identische Folge-
|
||||
// Events (z.B. zwei mal 'thinking' direkt hintereinander). Tool-
|
||||
@@ -1394,11 +1403,18 @@ const ChatScreen: React.FC = () => {
|
||||
// ein neuer Search-Hit kommt, damit alte Retries nicht den neuen
|
||||
// Scroll-Versuch durcheinanderbringen ("permanent springen"-Bug).
|
||||
const pendingScrollRetry = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
// Zaehler fuer fehlgeschlagene Scroll-Retries. Hartes Limit gegen
|
||||
// Endlos-Loops wenn das Item-Layout aus irgendwelchen Gruenden nie
|
||||
// verfuegbar wird (z.B. weil setMessages mitten in der Sequenz die
|
||||
// FlatList re-rendert).
|
||||
const scrollRetryCount = useRef<number>(0);
|
||||
const MAX_SCROLL_RETRIES = 3;
|
||||
const clearPendingScrollRetry = () => {
|
||||
if (pendingScrollRetry.current) {
|
||||
clearTimeout(pendingScrollRetry.current);
|
||||
pendingScrollRetry.current = null;
|
||||
}
|
||||
scrollRetryCount.current = 0;
|
||||
};
|
||||
|
||||
// Bei Search-Index-Wechsel zur entsprechenden Bubble scrollen.
|
||||
@@ -2152,13 +2168,24 @@ const ChatScreen: React.FC = () => {
|
||||
scrollEventThrottle={120}
|
||||
onScrollToIndexFailed={(info) => {
|
||||
// FlatList kennt das Item-Layout noch nicht. Wir scrollen grob in
|
||||
// die Naehe (Average-Item-Hoehe-Schaetzung) und versuchen EINMAL
|
||||
// nach 300ms praezise nachzusetzen. Mehr Retries → Endlos-Cascade
|
||||
// (jeder failed Retry triggert wieder den Handler → 3, 9, 27 ...
|
||||
// Scrolls in der Pipeline = der "permanent springen"-Bug).
|
||||
// die Naehe (Average-Item-Hoehe-Schaetzung) und versuchen bis zu
|
||||
// MAX_SCROLL_RETRIES mal praezise nachzusetzen. Danach geben wir
|
||||
// auf — User sieht die Bubble in der ungefaehren Naehe und kann
|
||||
// selber finetunen. Frueher: jeder failed Retry triggerte einen
|
||||
// neuen Retry ohne Limit → "permanent springen"-Bug, vor allem
|
||||
// wenn waehrenddessen setMessages die Layouts invalidierte.
|
||||
const offset = info.averageItemLength * info.index;
|
||||
try { flatListRef.current?.scrollToOffset({ offset, animated: false }); } catch {}
|
||||
clearPendingScrollRetry();
|
||||
if (pendingScrollRetry.current) {
|
||||
clearTimeout(pendingScrollRetry.current);
|
||||
pendingScrollRetry.current = null;
|
||||
}
|
||||
if (scrollRetryCount.current >= MAX_SCROLL_RETRIES) {
|
||||
// Aufgeben — Item ist offenbar nicht stabil renderbar
|
||||
scrollRetryCount.current = 0;
|
||||
return;
|
||||
}
|
||||
scrollRetryCount.current += 1;
|
||||
pendingScrollRetry.current = setTimeout(() => {
|
||||
pendingScrollRetry.current = null;
|
||||
try { flatListRef.current?.scrollToIndex({ index: info.index, animated: true, viewPosition: 0 }); } catch {}
|
||||
|
||||
Reference in New Issue
Block a user