';
host.innerHTML = html;
host.querySelectorAll('a[data-v3-plugin]').forEach((a) => {
a.addEventListener('click', (e) => {
e.preventDefault();
go('plugin-' + a.getAttribute('data-v3-plugin'));
});
});
}
// ── showScreen wrapper (idempotent rehydration — design/05 §Rehydration) ─
function installShowScreenHook() {
const hooks = window.__slopsmithV3ShellHooks || (window.__slopsmithV3ShellHooks = {});
hooks.syncActive = syncActive; // always point at the latest impl
if (hooks.installed) return;
hooks.installed = true;
hooks.baseShowScreen = window.showScreen;
window.showScreen = function (id) {
// Route every "go to the library" navigation to the v3 native Songs
// screen instead of the legacy #home library, so player-close,
// settings-back, the hidden legacy navbar, etc. all stay in v3.
const target = (id === 'home') ? 'v3-songs' : id;
const r = hooks.baseShowScreen ? hooks.baseShowScreen.call(this, target) : undefined;
try { hooks.syncActive && hooks.syncActive(target); } catch (e) { /* non-fatal */ }
return r;
};
}
// ── Boot ────────────────────────────────────────────────────────────────
async function boot() {
if (window.fbBrand) window.fbBrand.renderWordmark(document.getElementById('v3-brand'), { size: 'text-xl' });
renderSidebar();
renderTopbar();
ensureBackdrop();
installShowScreenHook();
renderPluginNav(); // async, non-blocking
// First-run gate: onboarding overlay is owned by prompt 15. Until it
// exists, degrade gracefully and go straight to the dashboard.
let profile = null;
try {
const res = await fetch('/api/profile');
if (res.ok) profile = await res.json();
} catch (e) { /* profile endpoint lands in prompt 15 */ }
if (profile && profile.onboarded === false && window.v3Onboarding && typeof window.v3Onboarding.show === 'function') {
try { window.v3Onboarding.show(profile); } catch (e) { /* fall through */ }
}
// Splitscreen pop-out windows (`?ssFollower=1`) get sent to
// showScreen('player') by app.js's bootstrap (app.js:9716) before
// this runs, exactly so the library doesn't flash on the popup.
// Don't undo that here — the splitscreen IIFE loads next and takes
// the popup the rest of the way into follower mode. Without this
// bail, the default 'v3-home' target below would re-activate the
// library screen and bring the flash back.
let isFollowerWindow = false;
try { isFollowerWindow = new URLSearchParams(location.search).get('ssFollower') === '1'; }
catch (_) { /* file:// or sandboxed iframe — fall through */ }
if (isFollowerWindow) {
syncActive('player');
} else {
// Deep-link IN on load: honor a #/key fragment, then strip it so
// subsequent screen-switch audio cleanup doesn't trip app.js's
// href-based empty-src guard (see syncActive). #v3-home is already
// `.active` in the HTML, so when that's the target we only sync the
// chrome — calling showScreen() redundantly would run its non-player
// teardown branch and log a spurious "Empty src" media error.
const m = (location.hash || '').match(/^#\/([\w-]+)/);
const entry = m && byKey(m[1]);
if (location.hash) {
try { history.replaceState(null, '', location.pathname + location.search); } catch (e) { /* file:// */ }
}
const target = entry ? entry.screen : 'v3-home';
const el = document.getElementById(target);
if (el && el.classList.contains('active')) {
syncActive(target);
} else {
go(target);
}
}
// The "Welcome back, {name}!" title needs the profile, which loads
// async — refresh once it's in (and whenever it changes).
function refreshHomeTitle() { if (currentScreenId() === 'v3-home') setTopbarTitle(titleFor('v3-home')); }
if (window.slopsmith && typeof window.slopsmith.on === 'function') {
window.slopsmith.on('v3:profile-updated', refreshHomeTitle);
}
setTimeout(refreshHomeTitle, 700);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot, { once: true });
} else {
boot();
}
})();