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 (_) {}