From b94626787b03b931b488a96bbdf75076a6c512a0 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Mon, 25 May 2026 10:28:57 +0200 Subject: [PATCH] =?UTF-8?q?fix(diagnostic):=20chat=5Fhistory-Render=20vert?= =?UTF-8?q?r=C3=A4gt=20kaputte=20Bubbles=20+=20EHOSTUNREACH=20skipped=20TL?= =?UTF-8?q?S-Fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zwei kleine Robustness-Verbesserungen: 1) chat_history-Handler im Frontend: jede Bubble jetzt in try/catch. Wenn eine Bubble bei der Render-Pipeline (escape/linkify/regex-replace) eine Exception wirft, brach die ganze for-Schleife ab und alle nachfolgenden Bubbles wurden nicht mehr in den DOM geschrieben — beim Reload sah man dann nur die ersten N Eintraege und Stefan dachte die letzten Antworten waeren weg. Jetzt: Fehler-Bubble mit "⚠ Render-Fehler" + console.error, restliche Bubbles laufen weiter durch. 2) Diagnostic-Server RVS-Reconnect: TLS-Fallback war auch bei reinen Netz-Fehlern (EHOSTUNREACH, ECONNREFUSED, ENETUNREACH, ETIMEDOUT, ENOTFOUND, EAI_AGAIN) gefeuert — bringt nichts weil der Server eh tot ist, generiert aber doppelte Reconnect-Versuche + Log-Spam. Jetzt nur noch bei wirklichen TLS/Handshake-Fehlern. Co-Authored-By: Claude Opus 4.7 (1M context) --- diagnostic/index.html | 68 +++++++++++++++++++++++++++---------------- diagnostic/server.js | 12 ++++++-- 2 files changed, 53 insertions(+), 27 deletions(-) diff --git a/diagnostic/index.html b/diagnostic/index.html index 303b317..c67deb8 100644 --- a/diagnostic/index.html +++ b/diagnostic/index.html @@ -1650,36 +1650,54 @@ if (msg.type === 'chat_history') { const boxes = [chatBox, document.getElementById('chat-box-fs')].filter(Boolean); for (const b of boxes) b.innerHTML = ''; + let errorCount = 0; if (msg.messages && msg.messages.length > 0) { - for (const m of msg.messages) { - if (m.type === 'aria_file') { - // ARIA-Datei-Bubble — addAriaFile schreibt selbst in beide Boxen - addAriaFile({ serverPath: m.serverPath, name: m.name, mimeType: m.mimeType, size: m.size, deleted: m.deleted }); - continue; - } - // [FILE: ...]-Marker rausfiltern (gleicher Filter wie addChat) - const cleaned = (m.text || '').replace(/\[FILE:\s*\/shared\/uploads\/[^\]]+\]/gi, '').replace(/\n{3,}/g, '\n\n').trim(); - const escaped = escapeHtml(cleaned); - let linked = linkifyText(escaped); - // /shared/uploads/-Bildpfade auch im History inline rendern - linked = linked.replace(/\/shared\/uploads\/[^\s<"]+\.(jpg|jpeg|png|gif|webp|svg|bmp)/gi, (match) => { - return `${match}`; - }); - const time = m.ts ? new Date(m.ts).toLocaleTimeString('de-DE') : '?'; - const trashBtn = m.ts - ? `` - : ''; - const innerHtml = `${trashBtn}${linked}
${escapeHtml(m.meta)} — ${time}
`; - for (const b of boxes) { - const el = document.createElement('div'); - el.className = `chat-msg ${m.type}`; - if (m.ts) el.dataset.ts = String(m.ts); - el.innerHTML = innerHtml; - b.appendChild(el); + for (let mi = 0; mi < msg.messages.length; mi++) { + const m = msg.messages[mi]; + try { + if (m.type === 'aria_file') { + addAriaFile({ serverPath: m.serverPath, name: m.name, mimeType: m.mimeType, size: m.size, deleted: m.deleted }); + continue; + } + const cleaned = (m.text || '').replace(/\[FILE:\s*\/shared\/uploads\/[^\]]+\]/gi, '').replace(/\n{3,}/g, '\n\n').trim(); + const escaped = escapeHtml(cleaned); + let linked = linkifyText(escaped); + linked = linked.replace(/\/shared\/uploads\/[^\s<"]+\.(jpg|jpeg|png|gif|webp|svg|bmp)/gi, (match) => { + return `${match}`; + }); + const time = m.ts ? new Date(m.ts).toLocaleTimeString('de-DE') : '?'; + const trashBtn = m.ts + ? `` + : ''; + const innerHtml = `${trashBtn}${linked}
${escapeHtml(m.meta)} — ${time}
`; + for (const b of boxes) { + const el = document.createElement('div'); + el.className = `chat-msg ${m.type}`; + if (m.ts) el.dataset.ts = String(m.ts); + el.innerHTML = innerHtml; + b.appendChild(el); + } + } catch (renderErr) { + // Eine kaputte Bubble darf nicht den Rest der History killen. + // Vorher passierte genau das: Frontend-Render bracht bei einer + // problematischen Antwort ab, alle nachfolgenden Nachrichten waren + // beim Reload weg. Jetzt: Fehler-Bubble einbauen + weitermachen. + errorCount++; + console.error('chat_history render error at idx ' + mi + ':', renderErr, m); + for (const b of boxes) { + const el = document.createElement('div'); + el.className = `chat-msg ${m.type || 'received'}`; + if (m.ts) el.dataset.ts = String(m.ts); + el.innerHTML = `⚠ Render-Fehler in Bubble (${escapeHtml(String(renderErr.message || renderErr))})
${m.ts ? new Date(m.ts).toLocaleTimeString('de-DE') : '?'}
`; + b.appendChild(el); + } } } for (const b of boxes) b.scrollTop = b.scrollHeight; } + if (errorCount > 0) { + console.warn(`chat_history: ${errorCount} Bubble(s) konnten nicht gerendert werden`); + } return; } diff --git a/diagnostic/server.js b/diagnostic/server.js index b9c62b5..fd5e5ff 100644 --- a/diagnostic/server.js +++ b/diagnostic/server.js @@ -701,8 +701,16 @@ function connectRVS(forcePlain) { state.rvs.lastError = err.message; broadcastState(); - // TLS Fallback - if (useTls && RVS_TLS_FALLBACK === "true" && !fallbackTriggered) { + // TLS-Fallback nur bei wirklichen TLS/Handshake-Fehlern. + // Bei Netz-Problemen wie EHOSTUNREACH, ECONNREFUSED, ENETUNREACH, + // EAI_AGAIN ist der Server eh tot — Fallback bringt nichts ausser + // Log-Spam und doppelten Retries. + const netErr = (err.code || err.message || "").toString(); + const isNetDown = + /^(EHOSTUNREACH|ECONNREFUSED|ENETUNREACH|ETIMEDOUT|EAI_AGAIN|ENOTFOUND)$/.test(netErr) || + /EHOSTUNREACH|ECONNREFUSED|ENETUNREACH|ETIMEDOUT|EAI_AGAIN|ENOTFOUND/.test(err.message || ""); + + if (useTls && RVS_TLS_FALLBACK === "true" && !fallbackTriggered && !isNetDown) { fallbackTriggered = true; log("warn", "rvs", "TLS fehlgeschlagen — Fallback auf ws://"); try { ws.removeAllListeners(); ws.close(); } catch (_) {}