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(= json_encode($prefix) ?>, = $meshDefault ? 'true' : 'false' ?>);