fix(app): kompletter Server-Sync bei Reconnect — Server ist Source of Truth
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) <noreply@anthropic.com>
This commit is contained in:
@@ -407,15 +407,17 @@ const ChatScreen: React.FC = () => {
|
|||||||
return;
|
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') {
|
if (message.type === 'chat_history_response') {
|
||||||
const p = (message.payload || {}) as any;
|
const p = (message.payload || {}) as any;
|
||||||
const incoming = (p.messages || []) as Array<any>;
|
const incoming = (p.messages || []) as Array<any>;
|
||||||
if (!incoming.length) return;
|
console.log(`[Chat] Server-Sync: ${incoming.length} Nachrichten vom Server`);
|
||||||
console.log(`[Chat] ${incoming.length} verpasste Nachrichten nachgeladen`);
|
const fromServer: ChatMessage[] = incoming.map(m => {
|
||||||
const toAdd: ChatMessage[] = incoming.map(m => {
|
|
||||||
const role = m.role === 'user' ? 'user' : 'aria';
|
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 files = Array.isArray(m.files) ? m.files : [];
|
||||||
const attachments = files.map((f: any) => ({
|
const attachments = files.map((f: any) => ({
|
||||||
type: (typeof f.mimeType === 'string' && f.mimeType.startsWith('image/')) ? 'image' : 'file',
|
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);
|
const maxTs = incoming.reduce((mx: number, m: any) => Math.max(mx, m.ts || 0), 0);
|
||||||
setMessages(prev => {
|
setMessages(prev => {
|
||||||
// Dedup auf ts-basis: nicht erneut adden wenn schon was bei +/- 1s vorhanden
|
// Lokal-only Bubbles erkennen + behalten:
|
||||||
const existingTs = new Set(prev.map(m => m.timestamp));
|
// - Skill-Created-Notifications (skillCreated gesetzt)
|
||||||
const newOnes = toAdd.filter(m => !existingTs.has(m.timestamp));
|
// - Laufende Sprachnachrichten ohne STT-Result (audioRequestId
|
||||||
return capMessages([...prev, ...newOnes]);
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -701,14 +715,13 @@ const ChatScreen: React.FC = () => {
|
|||||||
|
|
||||||
const unsubState = rvs.onStateChange((state) => {
|
const unsubState = rvs.onStateChange((state) => {
|
||||||
setConnectionState(state);
|
setConnectionState(state);
|
||||||
// Bei (re)connect: verpasste Chat-Eintraege seit der letzten gesehenen
|
// Bei (re)connect: KOMPLETTEN Server-Stand holen. Server ist die
|
||||||
// Nachricht abholen. lastChatSync wird beim Eingang von Nachrichten
|
// Source-of-Truth — wenn er leer ist (z.B. nach "Konversation
|
||||||
// hochgezaehlt; default 0 = alle (gecappt auf Server-Limit).
|
// 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') {
|
if (state === 'connected') {
|
||||||
AsyncStorage.getItem('aria_chat_last_sync').then(stored => {
|
rvs.send('chat_history_request' as any, { since: 0, limit: 200 });
|
||||||
const since = stored ? parseInt(stored, 10) || 0 : 0;
|
|
||||||
rvs.send('chat_history_request' as any, { since, limit: 100 });
|
|
||||||
}).catch(() => {});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user