diff --git a/frontend/app.js b/frontend/app.js
index 8da49bd..d808561 100755
--- a/frontend/app.js
+++ b/frontend/app.js
@@ -4729,8 +4729,7 @@ const Notes = (() => {
const v = $('stgVideo'), a = $('stgAudio');
if (v) v.classList.toggle('on', !ao); if (a) a.classList.toggle('on', ao);
}
- setInterval(chips, 1500);
- setTimeout(chips, 400);
+ VisibleTimer.setIntervalWhenVisible(chips, 1500, { immediate: true });
})();
// ============================================================================
@@ -5069,7 +5068,7 @@ const Remote = (() => {
// ==========================================================================
// Remote (this phone drives a host)
// ==========================================================================
- const rc = { ws: null, open: false, state: null, stateAt: 0, queue: { items: [], idx: -1 }, retry: null, ping: null, latency: null, online: false, seeking: false, volTimer: null, drag: null };
+ const rc = { ws: null, open: false, state: null, stateAt: 0, queue: { items: [], idx: -1 }, retry: null, ping: null, seekTimer: null, latency: null, online: false, seeking: false, volTimer: null, drag: null };
const rcSend = (m) => { if (rc.ws && rc.ws.readyState === 1) rc.ws.send(JSON.stringify(m)); };
const cmd = (name, args = {}) => rcSend({ type: 'cmd', cmd: name, ...args });
@@ -5299,6 +5298,7 @@ const Remote = (() => {
function openRemoteView({ code = '' } = {}) {
rc.open = true;
+ rc.seekTimer = VisibleTimer.setIntervalWhenVisible(renderSeek, 500);
// This phone is the remote now — don't also play here.
try { if (Player.master && !Player.master.paused) Player.pause(); } catch { /* nothing loaded */ }
$('remoteView').classList.remove('hidden');
@@ -5309,6 +5309,8 @@ const Remote = (() => {
function closeRemoteView() {
rc.open = false;
+ rc.seekTimer?.clear();
+ rc.seekTimer = null;
clearTimeout(rc.retry);
clearInterval(rc.ping);
const ws = rc.ws;
@@ -5387,7 +5389,6 @@ const Remote = (() => {
toast(next ? `Screen pauses in ${next} min` : 'Sleep timer off');
});
$('rvService').addEventListener('click', () => cmd('service', { on: !(rc.state && rc.state.service) }));
- setInterval(() => { if (rc.open) renderSeek(); }, 500);
// Hosts keep phones current once a second while anything plays.
setInterval(() => { if (host.ws && host.remotes.length) { pushState(); pushQueue(); } }, 1000);
document.addEventListener('visibilitychange', () => {
@@ -5800,7 +5801,7 @@ const CatalogSync = (() => {
}
}
window.addEventListener('online', flush);
- if (WEB) setInterval(flush, 60000);
+ if (WEB) VisibleTimer.setIntervalWhenVisible(flush, 60000, { immediate: true });
return { send, flush };
})();
function collectSearchMetadata(q, cards) { if (WEB) CatalogSync.send(q, cards); }
@@ -11061,7 +11062,7 @@ function setupViewportAnchorGuard() {
// Last-resort safety net for offsets none of the events above report:
// a 2s tick that reads scrollY (cheap) and snaps back only when displaced,
// so "nav taps silently do nothing" can never persist longer than a beat.
- setInterval(reanchor, 2000);
+ VisibleTimer.setIntervalWhenVisible(reanchor, 2000);
}
// ============================================================================
@@ -11304,7 +11305,7 @@ function rotateChips() {
}
});
}
-setInterval(rotateChips, 8000);
+VisibleTimer.setIntervalWhenVisible(rotateChips, 8000);
document.querySelectorAll('.chip').forEach((c) => {
c.addEventListener('click', () => {
@@ -12151,8 +12152,7 @@ function checkBuildTag() {
}
function pollBuildTag() {
- checkBuildTag();
- setInterval(checkBuildTag, 5 * 60 * 1000); // recheck every 5 minutes
+ VisibleTimer.setIntervalWhenVisible(checkBuildTag, 5 * 60 * 1000, { immediate: true });
}
// ============================================================================
diff --git a/frontend/index.html b/frontend/index.html
index b4de34a..f4b56f3 100755
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -693,6 +693,7 @@
+