Compare commits

...

2 Commits

Author SHA1 Message Date
duffyduck bbd51406a9 release: bump version to 0.1.5.2 2026-05-15 21:48:14 +02:00
duffyduck 2cd436f6e9 fix(chat): Such-Sprung praezise via Layout-Cache + Filter
Symptom: Suche nach 'cessna' sprang zur Oberhausen-Bubble (~15 Bubbles
daneben), egal welcher Versuch.

Zwei Ursachen:

1) searchMatchIds suchte in `messages` (alle Bubbles inkl. Memory/Skill/
   Trigger-Spezial-Bubbles), aber gescrollt wird in `invertedMessages`
   die diese filtert. Wenn 'cessna' nur in einer Memory-Bubble vorkam,
   war die ID in searchMatchIds aber nicht in invertedMessages →
   findIndex=-1 → kein Scroll, Pre-Scroll-Offset von voriger Aktion
   blieb sichtbar. Fix: searchMatchIds aus chatVisibleMessages.

2) AVG_BUBBLE_HEIGHT=150 als Pauschalschaetzung war zu grob — Voice-
   Bubbles sind ~70 px, lange ARIA-Antworten 400+. Pre-Scroll-Offset
   landete bei langen Listen weit daneben. Fix: itemHeights-Ref-Map
   wird per onLayout in renderMessage gefuettert. Pre-Scroll summiert
   echte gemessene Hoehen (Fallback AVG fuer noch nicht gerenderte) —
   beim zweiten Such-Versuch lernt der Cache, beim ersten klappt's
   schon besser als mit dem Pauschalwert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:42:08 +02:00
3 changed files with 37 additions and 18 deletions
+2 -2
View File
@@ -79,8 +79,8 @@ android {
applicationId "com.ariacockpit" applicationId "com.ariacockpit"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 10501 versionCode 10502
versionName "0.1.5.1" versionName "0.1.5.2"
// Fallback fuer Libraries mit Product Flavors // Fallback fuer Libraries mit Product Flavors
missingDimensionStrategy 'react-native-camera', 'general' missingDimensionStrategy 'react-native-camera', 'general'
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "aria-cockpit", "name": "aria-cockpit",
"version": "0.1.5.1", "version": "0.1.5.2",
"private": true, "private": true,
"scripts": { "scripts": {
"android": "react-native run-android", "android": "react-native run-android",
+34 -15
View File
@@ -1383,13 +1383,17 @@ const ChatScreen: React.FC = () => {
// Such-Treffer: alle Message-IDs die zur Query passen, in chronologischer // Such-Treffer: alle Message-IDs die zur Query passen, in chronologischer
// Reihenfolge (aelteste zuerst). Bei Query-Change resetten wir den Index. // Reihenfolge (aelteste zuerst). Bei Query-Change resetten wir den Index.
// WICHTIG: nur in chatVisibleMessages suchen — Spezial-Bubbles (Memory/
// Skill/Trigger) sind im Chat-Stream nicht sichtbar und Treffer auf die
// wuerden zu „ID nicht im FlatList → findIndex=-1 → kein Scroll"-Fail
// fuehren (Cessna in einer Memory-Bubble → springt zur falschen Stelle).
const searchMatchIds = useMemo(() => { const searchMatchIds = useMemo(() => {
const q = searchQuery.trim().toLowerCase(); const q = searchQuery.trim().toLowerCase();
if (!q) return [] as string[]; if (!q) return [] as string[];
return messages return chatVisibleMessages
.filter(m => (m.text || '').toLowerCase().includes(q)) .filter(m => (m.text || '').toLowerCase().includes(q))
.map(m => m.id); .map(m => m.id);
}, [messages, searchQuery]); }, [chatVisibleMessages, searchQuery]);
useEffect(() => { useEffect(() => {
setSearchIndex(0); setSearchIndex(0);
@@ -1425,11 +1429,11 @@ const ChatScreen: React.FC = () => {
// Den aktuellen Snapshot von invertedMessages holen wir via Ref. // Den aktuellen Snapshot von invertedMessages holen wir via Ref.
const invertedMessagesRef = useRef(invertedMessages); const invertedMessagesRef = useRef(invertedMessages);
invertedMessagesRef.current = invertedMessages; invertedMessagesRef.current = invertedMessages;
// Schaetzwert fuer durchschnittliche Bubble-Hoehe. Wird fuer den // Cache fuer echte Bubble-Hoehen, gefuettert per onLayout in
// ersten Pre-Scroll genutzt wenn FlatList noch keine Layouts hat. // renderMessage. Wird beim Pre-Scroll genutzt damit der grobe Sprung
// 150 px ist Mittel zwischen kurzen Voice-Bubbles (~70) und langen // praezise landet (statt mit dem 150-px-Pauschalwert weit daneben).
// ARIA-Antworten (~250). const itemHeights = useRef<Map<string, number>>(new Map());
const AVG_BUBBLE_HEIGHT = 150; const AVG_BUBBLE_HEIGHT = 150; // Fallback fuer noch nicht gemessene Items
useEffect(() => { useEffect(() => {
if (!searchMatchIds.length) { if (!searchMatchIds.length) {
lastSearchScrollKey.current = ''; lastSearchScrollKey.current = '';
@@ -1447,15 +1451,22 @@ const ChatScreen: React.FC = () => {
clearPendingScrollRetry(); clearPendingScrollRetry();
const idx = invertedMessagesRef.current.findIndex(m => m.id === id); const idx = invertedMessagesRef.current.findIndex(m => m.id === id);
if (idx < 0 || !flatListRef.current) return; if (idx < 0 || !flatListRef.current) return;
// Pre-Scroll: erst grob mit konstanter Hoehe in die Naehe springen. // Pre-Scroll: erst grob in die Naehe springen, damit FlatList die
// Damit hat FlatList ueberhaupt die Chance die Layouts der Bubbles in // Bubbles in der Umgebung ueberhaupt rendert (sonst basiert
// der Naehe zu rendern — sonst basiert info.averageItemLength im // averageItemLength im Failed-Handler nur auf den ersten ~10 Items
// Failed-Handler nur auf den ersten 10 Items (Default initialNumToRender) // und liefert einen voellig falschen Sprung).
// und liefert beim ersten Such-Versuch nach App-Start einen voellig // Offset = Summe echter Hoehen (aus itemHeights-Cache, gefuettert per
// falschen Sprung. // onLayout) + Fallback AVG fuer noch nicht gemessene. Bei „cold start"
// ist der Cache leer → AVG fuer alle → grob. Beim zweiten Such-Versuch
// sind die Bubbles in der Naehe gemessen → genauer.
let preOffset = 0;
const inv = invertedMessagesRef.current;
for (let i = 0; i < idx; i++) {
preOffset += itemHeights.current.get(inv[i].id) || AVG_BUBBLE_HEIGHT;
}
try { try {
flatListRef.current?.scrollToOffset({ flatListRef.current?.scrollToOffset({
offset: idx * AVG_BUBBLE_HEIGHT, offset: preOffset,
animated: false, animated: false,
}); });
} catch {} } catch {}
@@ -1885,7 +1896,15 @@ const ChatScreen: React.FC = () => {
} }
return ( return (
<View style={[styles.messageBubble, isUser ? styles.userBubble : styles.ariaBubble, searchHighlightStyle]}> <View
style={[styles.messageBubble, isUser ? styles.userBubble : styles.ariaBubble, searchHighlightStyle]}
onLayout={e => {
// Echte Hoehe in Cache speichern — Pre-Scroll der Suche nutzt
// die summierten Cache-Werte fuer praezisen Sprung. Bei
// unbekannten Items faellt's auf AVG_BUBBLE_HEIGHT zurueck.
itemHeights.current.set(item.id, e.nativeEvent.layout.height);
}}
>
{/* Anhang-Vorschau */} {/* Anhang-Vorschau */}
{item.attachments?.map((att, idx) => ( {item.attachments?.map((att, idx) => (
<View key={idx}> <View key={idx}>