Mesh chat: reader-side formatting toggle, poll backoff when hidden, Partnership always opens on mesh

This commit is contained in:
Gmer4Lfe
2026-08-17 17:12:27 -04:00
parent eab5cc911c
commit 10f2a0a8a4
+49 -8
View File
@@ -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.">
<input type="checkbox" id="vv-nc-follow" checked onchange="vvNcPref('follow')"> <span>Auto Scroll</span>
</label>
<!-- Reader's choice, not the sender's. Someone else's purple italic large is their idea of
emphasis, and on a wall-mounted dashboard it is just noise — this turns every message
back into plain text here without changing what they sent or what anyone else sees. -->
<label class="vv-ai-cb" style="font-size:10px;"
title="Render colours, fonts and bold as sent. Unticked shows every message as plain text.">
<input type="checkbox" id="vv-nc-fmt-on" checked onchange="vvNcPref('fmtOn');vvNcRender()"> <span>Formatting</span>
</label>
<span id="vv-nc-status" style="margin-left:auto;font-size:9px;color:#3a3a3a;"></span>
</div>
@@ -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' ?>);