diff --git a/Plugin/unraid/include/node_chat.php b/Plugin/unraid/include/node_chat.php index 817b480..7bedb64 100644 --- a/Plugin/unraid/include/node_chat.php +++ b/Plugin/unraid/include/node_chat.php @@ -257,6 +257,13 @@ function vv_nc_pane_markup(string $prefix, bool $meshDefault = false): void { title="Follow the newest message. Unticks when you scroll up, re-ticks at the bottom."> Auto Scroll + + @@ -347,7 +354,8 @@ function _vvNcLoadPrefs() { if (el && p[k] != null) el.value = p[k]; } for (const k of ['names', 'follow']) { - const el = document.getElementById('vv-nc-' + k); + const id = k === 'fmtOn' ? 'vv-nc-fmt-on' : 'vv-nc-' + k; + const el = document.getElementById(id); if (el && p[k] != null) el.checked = !!p[k]; } _vvNc.fmt = Object.assign(_vvNc.fmt, p.fmt || {}); @@ -356,7 +364,8 @@ function _vvNcLoadPrefs() { function vvNcPref() { const p = { fmt: _vvNc.fmt }; for (const k of ['color', 'font', 'size']) p[k] = document.getElementById('vv-nc-' + k)?.value ?? ''; - for (const k of ['names', 'follow']) p[k] = !!document.getElementById('vv-nc-' + k)?.checked; + for (const k of ['names', 'follow', 'fmtOn']) + p[k] = !!document.getElementById(k === 'fmtOn' ? 'vv-nc-fmt-on' : 'vv-nc-' + k)?.checked; try { localStorage.setItem(_VV_NC_PREF, JSON.stringify(p)); } catch (_) {} _vvNcPaintFmt(); } @@ -390,6 +399,12 @@ function vvNcToggleFmt() { // Style string for a message. Closed set on both ends — the server validates the same values, // so nothing here can emit a property the store did not agree to. function _vvNcStyle(m, mine) { + // Reader wins over sender. Someone else's purple italic large is their idea of emphasis, and + // on a wall-mounted dashboard it is noise — this renders every message plain here without + // changing what they sent or what anyone else sees. The only thing kept is the you/them colour + // distinction, which is orientation rather than decoration. + if (!document.getElementById('vv-nc-fmt-on')?.checked) + return `color:${mine ? '#7a9a7a' : '#9aa'};font-size:12px;`; const st = m.style || {}; const sizes = { sm: '11px', lg: '14px' }; let s = `color:${st.color || (mine ? '#7a9a7a' : '#9aa')};`; @@ -544,22 +559,48 @@ function vvNcMode(force) { const t = document.getElementById(p + '-title'); if (t) t.textContent = _vvNcMeshOn ? 'Mesh Chat' : (t.dataset.ai || t.textContent); try { localStorage.setItem('vvNcMode:' + p, _vvNcMeshOn ? '1' : '0'); } catch (_) {} - if (_vvNcMeshOn) vvNcLoad(); + // Opening the mesh resets the backoff, so switching to it never waits on a 90-second timer. + if (_vvNcMeshOn) { _vvNcStep = 0; vvNcLoad(); } + if (typeof _vvNcSchedule === 'function') _vvNcSchedule(); } function vvNcInit(prefix, meshDefault) { _vvNcPrefix = prefix; const t = document.getElementById(prefix + '-title'); if (t && !t.dataset.ai) t.dataset.ai = t.textContent.trim(); + // meshDefault means "this page IS the mesh page" — Partnership opens on it every time rather + // than on whatever was last toggled. Everywhere else the choice is remembered, because there + // the mesh is a thing you switch to and being returned to it is the surprise. let want = !!meshDefault; - try { - const s = localStorage.getItem('vvNcMode:' + prefix); - if (s !== null) want = s === '1'; - } catch (_) {} + if (!meshDefault) { + try { + const s = localStorage.getItem('vvNcMode:' + prefix); + if (s !== null) want = s === '1'; + } catch (_) {} + } _vvNcLoadPrefs(); vvNcMode(want); vvNcChans(); - setInterval(vvNcChans, 15000); + _vvNcSchedule(); +} + +// Poll fast while the mesh is on screen, and back off while it is not. +// +// Eight pages each asking every 15 seconds is eight times the traffic for one store, and while +// the card is showing the assistant the only thing the poll feeds is the unread badge — which +// nobody is watching to the second. Backs off 30 → 60 → 90 and stays there; snaps back to 15 the +// moment the mesh pane is opened, so switching to it is never waiting on a slow timer. +let _vvNcTimer = null, _vvNcStep = 0; +const _VV_NC_BACKOFF = [30000, 60000, 90000]; + +function _vvNcSchedule() { + if (_vvNcTimer) clearTimeout(_vvNcTimer); + const wait = _vvNcMeshOn ? 15000 : _VV_NC_BACKOFF[Math.min(_vvNcStep, _VV_NC_BACKOFF.length - 1)]; + _vvNcTimer = setTimeout(() => { + vvNcChans(); + if (!_vvNcMeshOn) _vvNcStep++; + _vvNcSchedule(); + }, wait); } vvNcInit(, );