From 3497aa23f848e239a7d746d52c1c35b4bbae0392 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Mon, 11 May 2026 23:55:25 +0200 Subject: [PATCH] =?UTF-8?q?fix(app):=20kompletter=20Server-Sync=20bei=20Re?= =?UTF-8?q?connect=20=E2=80=94=20Server=20ist=20Source=20of=20Truth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Symptom: Diagnostic-Server hat leere Chat-History (z.B. nach "Konversation zuruecksetzen" oder Wipe), App zeigt aber weiterhin ihren alten lokalen Stand. Wer das Wipe-Event verpasst hat (App offline), bleibt veraltet. Ursache: App schickte beim Reconnect chat_history_request {since: lastSync} und ignorierte leere Antworten. Wenn der Server ueberhaupt nichts mehr hat liefert er korrekt [] zurueck — App behielt aber lokalen State. Fix: - App schickt jetzt {since: 0, limit: 200} → KOMPLETTER Server-Stand - Handler ersetzt die persistierte Chat-History mit dem Server-Stand (statt zu mergen) - Lokal-only Bubbles bleiben erhalten: * Skill-Created-Notifications (skillCreated gesetzt) * Laufende Sprachnachrichten ohne STT-Result (audioRequestId gesetzt und text leer/Placeholder) - Wenn Server leer: lastSync ebenfalls geloescht (sauberer Restart-State) Co-Authored-By: Claude Opus 4.7 (1M context) --- android/src/screens/ChatScreen.tsx | 47 +++++++++++++++++++----------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx index b254f64..a39ba23 100644 --- a/android/src/screens/ChatScreen.tsx +++ b/android/src/screens/ChatScreen.tsx @@ -407,15 +407,17 @@ const ChatScreen: React.FC = () => { return; } - // chat_history_response: verpasste Nachrichten nachladen (bei Reconnect) + // chat_history_response: kompletter Server-Stand. App ersetzt ihre + // persistierte Chat-History damit. Lokal-only Bubbles (laufende + // Voice-Aufnahmen ohne STT-Result, Skill-Created-Events ohne + // text) bleiben erhalten — die sind durch fehlendes 'text' oder + // skillCreated/audioRequestId klar als "lokal" erkennbar. if (message.type === 'chat_history_response') { const p = (message.payload || {}) as any; const incoming = (p.messages || []) as Array; - if (!incoming.length) return; - console.log(`[Chat] ${incoming.length} verpasste Nachrichten nachgeladen`); - const toAdd: ChatMessage[] = incoming.map(m => { + console.log(`[Chat] Server-Sync: ${incoming.length} Nachrichten vom Server`); + const fromServer: ChatMessage[] = incoming.map(m => { const role = m.role === 'user' ? 'user' : 'aria'; - // ARIA-File-Marker aus dem Backup als attachments rekonstruieren const files = Array.isArray(m.files) ? m.files : []; const attachments = files.map((f: any) => ({ type: (typeof f.mimeType === 'string' && f.mimeType.startsWith('image/')) ? 'image' : 'file', @@ -434,12 +436,24 @@ const ChatScreen: React.FC = () => { }); const maxTs = incoming.reduce((mx: number, m: any) => Math.max(mx, m.ts || 0), 0); setMessages(prev => { - // Dedup auf ts-basis: nicht erneut adden wenn schon was bei +/- 1s vorhanden - const existingTs = new Set(prev.map(m => m.timestamp)); - const newOnes = toAdd.filter(m => !existingTs.has(m.timestamp)); - return capMessages([...prev, ...newOnes]); + // Lokal-only Bubbles erkennen + behalten: + // - Skill-Created-Notifications (skillCreated gesetzt) + // - Laufende Sprachnachrichten ohne STT-Result (audioRequestId + // gesetzt UND text leer/Placeholder) + const localOnly = prev.filter(m => + m.skillCreated || + (m.audioRequestId && (!m.text || m.text === '🎙 Aufnahme...' || m.text === 'Aufnahme...')) + ); + // Server-Stand + lokal-only (chronologisch sortiert) + const merged = [...fromServer, ...localOnly].sort((a, b) => a.timestamp - b.timestamp); + return capMessages(merged); }); - if (maxTs > 0) AsyncStorage.setItem('aria_chat_last_sync', String(maxTs)).catch(() => {}); + if (maxTs > 0) { + AsyncStorage.setItem('aria_chat_last_sync', String(maxTs)).catch(() => {}); + } else { + // Server leer → unsere lastSync auch zuruecksetzen + AsyncStorage.removeItem('aria_chat_last_sync').catch(() => {}); + } return; } @@ -701,14 +715,13 @@ const ChatScreen: React.FC = () => { const unsubState = rvs.onStateChange((state) => { setConnectionState(state); - // Bei (re)connect: verpasste Chat-Eintraege seit der letzten gesehenen - // Nachricht abholen. lastChatSync wird beim Eingang von Nachrichten - // hochgezaehlt; default 0 = alle (gecappt auf Server-Limit). + // Bei (re)connect: KOMPLETTEN Server-Stand holen. Server ist die + // Source-of-Truth — wenn er leer ist (z.B. nach "Konversation + // zuruecksetzen"), soll die App das spiegeln, auch wenn sie offline + // war als das passiert ist. since=0 + limit=200 → die letzten 200 + // Nachrichten vom Server, oder leeres Array wenn Server leer. if (state === 'connected') { - AsyncStorage.getItem('aria_chat_last_sync').then(stored => { - const since = stored ? parseInt(stored, 10) || 0 : 0; - rvs.send('chat_history_request' as any, { since, limit: 100 }); - }).catch(() => {}); + rvs.send('chat_history_request' as any, { since: 0, limit: 200 }); } });