fix(chat): Offline-Bubble verschwand nach Reconnect — clientMsgId-Dedup
Race-Bug nach Etappe 3: Beim Reconnect schickt die App parallel chat_history_request und (via flushQueuedMessages) die offline gestaute Nachricht. Die history_response kam an bevor die Bridge die Bubble in chat_backup.jsonl geschrieben hatte → Server-Liste ohne unsere Bubble → Merge ersetzte den lokalen Stand → Bubble weg (im Diagnostic war sie gleich danach drin). Bridge: _append_chat_backup nimmt clientMsgId mit auf. send_to_core reicht sie als kwarg durch (chat- und audio-Pfad). App: chat_history_response-Merge dedupt per clientMsgId. Lokale User- Bubbles deren clientMsgId der Server noch nicht kennt bleiben erhalten (localOnly-Filter erweitert). Server-User-Bubbles mit clientMsgId kriegen deliveryStatus='delivered' damit das ✓✓ auch nach Reload sichtbar bleibt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -615,6 +615,10 @@ const ChatScreen: React.FC = () => {
|
||||
mimeType: f.mimeType || '',
|
||||
serverPath: f.serverPath || '',
|
||||
})) as Attachment[];
|
||||
// clientMsgId weiterreichen — Bridge spiegelt sie im chat_backup,
|
||||
// damit wir lokale Bubbles per ID dedupen koennen statt nur per
|
||||
// Text/Timestamp-Heuristik.
|
||||
const cmid = typeof m.clientMsgId === 'string' ? m.clientMsgId : undefined;
|
||||
return {
|
||||
id: nextId(),
|
||||
sender: role as 'user' | 'aria',
|
||||
@@ -622,19 +626,32 @@ const ChatScreen: React.FC = () => {
|
||||
timestamp: m.ts || Date.now(),
|
||||
attachments: attachments.length ? attachments : undefined,
|
||||
backupTs: typeof m.ts === 'number' ? m.ts : undefined,
|
||||
...(cmid && { clientMsgId: cmid }),
|
||||
// Server-Bubble = vom Brain verarbeitet → 'delivered' (✓✓)
|
||||
...(role === 'user' && cmid && { deliveryStatus: 'delivered' as const }),
|
||||
};
|
||||
});
|
||||
const maxTs = incoming.reduce((mx: number, m: any) => Math.max(mx, m.ts || 0), 0);
|
||||
setMessages(prev => {
|
||||
// ClientMsgIds die der Server kennt — lokale Bubbles mit der
|
||||
// gleichen ID werden durch die Server-Version ersetzt.
|
||||
const serverCmids = new Set(
|
||||
fromServer.map(s => s.clientMsgId).filter((x): x is string => !!x)
|
||||
);
|
||||
// Lokal-only Bubbles erkennen + behalten:
|
||||
// - Skill-Created-Notifications (skillCreated gesetzt)
|
||||
// - Laufende Sprachnachrichten ohne STT-Result (audioRequestId
|
||||
// gesetzt UND text leer/Placeholder)
|
||||
// - User-Bubbles deren clientMsgId der Server noch nicht kennt:
|
||||
// z.B. waehrend Reconnect-Race oder solange flushQueuedMessages
|
||||
// noch laeuft. Ohne diesen Schutz haette der history_response
|
||||
// die gerade reaktivierten Offline-Nachrichten geloescht.
|
||||
const localOnly = prev.filter(m =>
|
||||
m.skillCreated ||
|
||||
m.triggerCreated ||
|
||||
m.memorySaved ||
|
||||
(m.audioRequestId && (!m.text || m.text === '🎙 Aufnahme...' || m.text === 'Aufnahme...'))
|
||||
(m.audioRequestId && (!m.text || m.text === '🎙 Aufnahme...' || m.text === 'Aufnahme...')) ||
|
||||
(m.sender === 'user' && m.clientMsgId && !serverCmids.has(m.clientMsgId))
|
||||
);
|
||||
// Server-Stand + lokal-only (chronologisch sortiert)
|
||||
const merged = [...fromServer, ...localOnly].sort((a, b) => a.timestamp - b.timestamp);
|
||||
|
||||
Reference in New Issue
Block a user