window.App = window.App || {};
App.version = App.version || {};
(function() {
const VERSION_URL = '/api/version';
const POLL_INTERVAL_MS = 60000;
// Baseline manifest captured on startup: { version, files: { rel: hash } }.
let baseline = null;
let timer = null;
// Set once a JS/HTML change is detected; the page reloads at a safe moment.
let reloadPending = false;
let checking = false;
async function fetchVersion() {
const resp = await fetch(VERSION_URL, { cache: 'no-store' });
if (!resp.ok) throw new Error('version fetch failed: ' + resp.status);
return resp.json();
}
// Swap a stylesheet in place using a cache-busted href so updated CSS
// applies instantly. The old link is removed only after the new one loads to
// avoid a flash of unstyled content.
function hotReloadCss(relPath, hash) {
const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]'));
const match = links.find((l) => {
const href = (l.getAttribute('href') || '').split('?')[0];
return href.endsWith(relPath) || href.endsWith('/' + relPath);
});
if (!match) return false;
const base = (match.getAttribute('href') || '').split('?')[0];
const fresh = match.cloneNode(false);
fresh.setAttribute('href', base + '?v=' + hash);
fresh.addEventListener('load', () => { if (match.parentNode) match.remove(); });
fresh.addEventListener('error', () => { if (fresh.parentNode) fresh.remove(); });
match.parentNode.insertBefore(fresh, match.nextSibling);
return true;
}
function diffFiles(oldFiles, newFiles) {
const changed = [];
const keys = new Set([
...Object.keys(oldFiles || {}),
...Object.keys(newFiles || {})
]);
keys.forEach((k) => {
if ((oldFiles || {})[k] !== (newFiles || {})[k]) changed.push(k);
});
return changed;
}
// A reload is "safe" when the user isn't mid-playback: no open video modal,
// no active reels feed, and no playing