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