diff --git a/Plugin/unraid/Varaverk.page b/Plugin/unraid/Varaverk.page index ebf07bf..812c8e7 100644 --- a/Plugin/unraid/Varaverk.page +++ b/Plugin/unraid/Varaverk.page @@ -92,6 +92,37 @@ function vvEscAttr(s) { .replace(//g,'>'); } +// ═══════════════════════════════════════════════════════════════════════════════════════════════ +// What a failed fetch says. +// +// Every fetch in this plugin used to end in `.catch(() => {})` — 24 of them. That is not error +// handling, it is error deletion: the request fails, nothing renders, nothing is logged, and the +// surface either sits on "Loading…" forever or silently keeps showing stale numbers. The mesh chat +// spent an unknown amount of time "taking a minute to load" because a ReferenceError was thrown on +// every render and swallowed here; the fault named itself the moment a catch reported it. +// +// Console always, because a poller that drops one tick should not shout on screen. A target +// element when the caller has one, because a panel that will otherwise never fill has to say why. +// +// Global for the same reason vvEscHtml is: pages/*.php are included one at a time and each would +// otherwise carry its own copy, which is the arrangement that lets two of them drift. +function vvFetchErr(where, e, el) { + const msg = (e && e.message) ? e.message : String(e || 'request failed'); + try { console.warn('[varaverk] ' + where + ' — ' + msg, e); } catch (_) {} + if (el) { + const n = (typeof el === 'string') ? document.getElementById(el) : el; + if (n) { n.textContent = where + ' failed: ' + msg; n.style.color = '#a05a2c'; } + } +} + +// Throws on a non-2xx instead of handing HTML to JSON.parse. Unraid answers an expired session +// with a 302 to the login page, so without this the reported error is "Unexpected token '<'", +// which names the symptom and hides the cause. +function vvJson(r) { + if (!r.ok) throw new Error('HTTP ' + r.status); + return r.json(); +} + // A URL about to be put in href/src or handed to window.open. Anything that is not plainly http, // https or a site-relative path becomes empty — javascript: is the one that matters, and an // allowlist is the only way to say that without chasing encodings. include/docs.php applies the diff --git a/Plugin/unraid/include/ai_chat.php b/Plugin/unraid/include/ai_chat.php index 96c575d..6934942 100644 --- a/Plugin/unraid/include/ai_chat.php +++ b/Plugin/unraid/include/ai_chat.php @@ -1440,7 +1440,7 @@ vv_ai_profiles_script(); fetch(API, { method: 'POST', headers: POST_HEAD, body: new URLSearchParams({ action: 'stop', token: t }) }) .then(r => r.json()) - .catch(() => {}); + .catch(e => vvFetchErr('stop generation', e)); // Nothing is rendered from the response. The poll already owns turning a terminal state into // a message, and having two paths do it is how a turn ends up in the transcript twice. } @@ -1465,7 +1465,7 @@ vv_ai_profiles_script(); noteLine('Stopped before anything was written.'); } fetch(API, { method: 'POST', headers: POST_HEAD, - body: new URLSearchParams({ action: 'clear', token }) }).catch(() => {}); + body: new URLSearchParams({ action: 'clear', token }) }).catch(e => vvFetchErr('clear job', e)); finish(); save(); return; @@ -1475,7 +1475,7 @@ vv_ai_profiles_script(); addAnswer(j); messages.push({ role: 'assistant', content: j.answer }); fetch(API, { method: 'POST', headers: POST_HEAD, - body: new URLSearchParams({ action: 'clear', token }) }).catch(() => {}); + body: new URLSearchParams({ action: 'clear', token }) }).catch(e => vvFetchErr('clear job', e)); finish(); save(); // The answer is passed so a page can react to what was said, not just that a turn @@ -1519,7 +1519,7 @@ vv_ai_profiles_script(); }) }) .then(r => r.json()) .then(d => { if (d.ok) { chatId = d.id; onChats(chatId); } }) - .catch(() => {}); + .catch(e => vvFetchErr('save conversation', e)); } @@ -2121,7 +2121,7 @@ vv_ai_profiles_script(); const last = pool.slice().sort((a, b) => (b.updated || 0) - (a.updated || 0))[0]; if (last) loadChat(last.id); - }).catch(() => {}); + }).catch(e => vvFetchErr('resume conversation', e)); } const inst = { @@ -2305,7 +2305,7 @@ vv_ai_profiles_script(); function load() { fetch(API + '?action=chats').then(r => r.json()) .then(d => { if (d.ok) { rows = d.chats || []; render(); } }) - .catch(() => {}); + .catch(e => vvFetchErr('conversation list', e)); } box.addEventListener('click', e => { @@ -2328,7 +2328,7 @@ vv_ai_profiles_script(); // from the store, which would silently resurrect it on the next turn. if (del.dataset.del === activeId && o.chat) o.chat.newChat(); load(); - }).catch(() => {}); + }).catch(e => vvFetchErr('delete conversation', e)); return; } const row = e.target.closest('.vv-ai-crow'); diff --git a/Plugin/unraid/include/confui.php b/Plugin/unraid/include/confui.php index bbd49ed..f75240a 100644 --- a/Plugin/unraid/include/confui.php +++ b/Plugin/unraid/include/confui.php @@ -121,7 +121,7 @@ function vv_ai_findings_strip(string $prefix, array $kinds, string $title): void `; }).join(''); }) - .catch(() => {}); + .catch(e => vvFetchErr('repair findings', e)); } // Delegated, and it reloads rather than mutating the row: the store is the truth and a card that diff --git a/Plugin/unraid/pages/ai.php b/Plugin/unraid/pages/ai.php index df54f52..285c178 100644 --- a/Plugin/unraid/pages/ai.php +++ b/Plugin/unraid/pages/ai.php @@ -618,7 +618,7 @@ vv_ai_chat_markup('vv-ai', [ function loadBanner(live) { fetch(API + '?action=stats' + (live ? '&live=1' : '')).then(r => r.json()) .then(d => { if (d.ok) renderBanner(d.stats); }) - .catch(() => {}); + .catch(e => vvFetchErr('AI banner', e)); } // ── Bug reports ───────────────────────────────────────────────────────── @@ -628,7 +628,7 @@ vv_ai_chat_markup('vv-ai', [ function loadBugs() { fetch(API + '?action=bugs').then(r => r.json()) .then(d => { if (d.ok) renderBugs(d.bugs || []); }) - .catch(() => {}); + .catch(e => vvFetchErr('AI bug list', e)); } function renderBugs(bugs) { @@ -750,7 +750,7 @@ vv_ai_chat_markup('vv-ai', [ fetch(API, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, body: new URLSearchParams({ action: 'bug_close', id, open: '0' }) }) - .then(() => loadBugs()).catch(() => {}); + .then(() => loadBugs()).catch(e => vvFetchErr('AI bug report', e)); }; // ── Findings & proposals ──────────────────────────────────────────────── @@ -1108,7 +1108,7 @@ vv_ai_chat_markup('vv-ai', [ function loadTokens() { fetch(API + '?action=tokens').then(r => r.json()) .then(d => { if (d.ok) { tokData = d.tokens; renderTokens(); } }) - .catch(() => {}); + .catch(e => vvFetchErr('AI token ledger', e)); } function renderTokens() { diff --git a/Plugin/unraid/pages/monitor.php b/Plugin/unraid/pages/monitor.php index cb8fe2c..2216d7c 100644 --- a/Plugin/unraid/pages/monitor.php +++ b/Plugin/unraid/pages/monitor.php @@ -2087,7 +2087,7 @@ function vvLoadTokens() { return fetch('/plugins/varaverk/api/ai.php?action=tokens') .then(r => r.json()) .then(d => { if (d.ok) { vvTokData = d.tokens; vvRenderTokens(); } }) - .catch(() => {}); + .catch(e => vvFetchErr('token ledger', e)); } function vvRenderTokens() { @@ -2522,7 +2522,7 @@ function vvPollStreams() { } vvRenderStreams(); }) - .catch(() => {}); + .catch(e => vvFetchErr('media streams', e)); } // An unreachable partner is stated, never folded into the totals as zero. "Nobody is watching diff --git a/Plugin/unraid/pages/partnership.php b/Plugin/unraid/pages/partnership.php index b765699..ecf8a61 100644 --- a/Plugin/unraid/pages/partnership.php +++ b/Plugin/unraid/pages/partnership.php @@ -1274,7 +1274,7 @@ function vvPtLoad() { fetch('/plugins/varaverk/api/partnership.php') .then(r => r.json()) .then(_render) - .catch(() => {}); + .catch(e => vvFetchErr('partnership status', e)); } _vvPtReload = vvPtLoad; // expose to top-level toggle fns diff --git a/Plugin/unraid/pages/rsync.php b/Plugin/unraid/pages/rsync.php index f0dfeb3..872dcb9 100644 --- a/Plugin/unraid/pages/rsync.php +++ b/Plugin/unraid/pages/rsync.php @@ -1074,7 +1074,7 @@ function vvRyLoad() { fetch('/plugins/varaverk/api/rsync.php') .then(r => r.json()) .then(_render) - .catch(() => {}); + .catch(e => vvFetchErr('rsync config', e)); } vvRyLoad(); @@ -1499,7 +1499,7 @@ function vvRpLoad() { _vvRpFillForm(_vvRpSelected); } }) - .catch(() => {}); + .catch(e => vvFetchErr('rsync profiles', e)); } function _vvRpPopulateSelector() { @@ -1656,7 +1656,7 @@ function vvMsStop() { fetch('/plugins/varaverk/api/manual_sync.php', { method: 'POST', body: fd }) .then(r => r.json()) .then(() => {}) - .catch(() => {}) + .catch(e => vvFetchErr('manual sync start', e)) .finally(() => { btn.textContent = '■ Stop'; btn.disabled = false; }); } @@ -2018,7 +2018,7 @@ function _vvMsStartPoll(token, btn, stat, out, badge, stopBtn) { setTimeout(() => { badge.style.display = 'none'; stat.textContent = ''; }, 6000); } }) - .catch(() => {}); + .catch(e => vvFetchErr('manual sync poll', e)); }, 1500); } @@ -2039,7 +2039,7 @@ function vvMsLoadRecent() { fetch(_MS_API + '?action=recent') .then(r => r.json()) .then(d => { _vvMsRecent = (d && d.rows) || []; _msRenderRecent(); }) - .catch(() => {}); + .catch(e => vvFetchErr('recent syncs', e)); } function _msRenderRecent() { @@ -2098,7 +2098,7 @@ document.addEventListener('click', async ev => { body: new URLSearchParams(body) }) .then(r => r.json()) .then(() => vvMsLoadRecent()) - .catch(() => {}); + .catch(e => vvFetchErr('manual sync action', e)); }); // Fills the form and stops. Deliberately does not run: one click away from a transfer that may diff --git a/Plugin/unraid/pages/scheduler.php b/Plugin/unraid/pages/scheduler.php index 2a8902e..a6cdac0 100644 --- a/Plugin/unraid/pages/scheduler.php +++ b/Plugin/unraid/pages/scheduler.php @@ -1567,7 +1567,7 @@ function vvPollStatus() { } vvRunningSet = nowRunning; }) - .catch(() => {}); + .catch(e => vvFetchErr('job status', e)); } function vvStartStatusPoll() { @@ -2785,7 +2785,7 @@ function vvBoardPoll() { vvUpdateErrors(d.errors ?? []); vvUpdatePartner(d.partner); }) - .catch(() => {}); + .catch(e => vvFetchErr('script board', e)); } function vvUpdateLocks(locks) { diff --git a/Plugin/unraid/pages/setup.php b/Plugin/unraid/pages/setup.php index ab68ef3..b5ac2fa 100644 --- a/Plugin/unraid/pages/setup.php +++ b/Plugin/unraid/pages/setup.php @@ -662,7 +662,7 @@ function vvStartPhase2Watch(sinceMs) { const done = (d.items || []).some(i => i.id === 'partnership' && i.ok); if (done || Date.now() > deadline) stop(); }) - .catch(() => {}); + .catch(e => vvFetchErr('setup checklist', e)); }; // Redraw between checklist polls so the elapsed counter moves. Without it the panel is static