diff --git a/public/admin/preview.html b/public/admin/preview.html index 11a1372..0640d0b 100644 --- a/public/admin/preview.html +++ b/public/admin/preview.html @@ -34,6 +34,22 @@ document.head.insertAdjacentHTML('beforeend', ``); await requireAuth(); +// Surface client-side errors on screen — a silent throw here used to leave the +// panel stuck on "Waiting for server…" with no clue why. +function showError(msg) { + let el = document.getElementById('perr'); + if (!el) { + el = document.createElement('div'); + el.id = 'perr'; + el.style.cssText = 'position:fixed;bottom:0;left:0;right:0;max-height:35vh;overflow:auto;' + + 'background:rgba(120,0,0,.9);color:#fff;font:12px monospace;padding:8px;z-index:9999;white-space:pre-wrap'; + document.body.appendChild(el); + } + el.textContent += msg + '\n'; +} +window.addEventListener('error', e => showError('[error] ' + e.message + (e.filename ? ` (${e.filename}:${e.lineno})` : ''))); +window.addEventListener('unhandledrejection', e => showError('[rejection] ' + (e.reason?.message || e.reason))); + const COLORS = { Red: '#ff2678', Yellow: '#fff35d', Blue: '#51eaf1' }; const app = document.getElementById('app'); app.innerHTML = `${nav('preview')} @@ -135,7 +151,13 @@ const clockSamples = []; let clockOffset = 0; async function addGhost(rec) { if (active.has(rec.uid)) return; - const group = await buildGhost(rec, gradients, modelManifest, characters); + let group; + try { + group = await buildGhost(rec, gradients, modelManifest, characters); + } catch (e) { + showError(`ghost "${rec.name}" failed to build: ${e.message}`); + return; + } group.userData.setHeight(ghostHeight); const label = textSprite(rec.name, COLORS[rec.color] || '#fff'); label.scale.set(20, 5, 1);