From 40198fa0cf0222fc62abfcb589cfb0f8de302298 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 26 Feb 2026 09:33:57 +0000 Subject: [PATCH] Add centralized error handling with UI toast for user-facing errors Co-Authored-By: Claude Opus 4.6 --- static/script.js | 27 +++++++++++++++++++-------- static/style.css | 20 ++++++++++++++++++++ 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/static/script.js b/static/script.js index 651a4375..ecbf64ac 100644 --- a/static/script.js +++ b/static/script.js @@ -26,6 +26,17 @@ const DEBUG = false; function debugLog(...args) { if (DEBUG) console.log(...args); } +function handleError(context, error, showUI = false) { + console.error(`[${context}]`, error); + if (showUI) { + const msg = document.createElement('div'); + msg.className = 'error-toast'; + msg.textContent = `${context}: ${error.message || 'Unknown error'}`; + document.body.appendChild(msg); + setTimeout(() => msg.remove(), GLOW_DURATION_MS); + } +} + /* ---------- DOM references --------------------------------------- */ const wrap = document.getElementById('mapContainer'); const group = document.getElementById('mapGroup'); @@ -574,7 +585,7 @@ async function fetchHeatmapData() { debugLog(`Loaded ${heatmapData.length} heat map points from last ${data.hours_window} hours`); renderHeatmap(); } catch (err) { - console.error('Failed to fetch heat map data:', err); + handleError('Heatmap', err); } } @@ -659,7 +670,7 @@ async function fetchPortalData() { debugLog(`Loaded ${portalData.length} portals from last hour`); renderPortals(); } catch (err) { - console.error('Failed to fetch portal data:', err); + handleError('Portals', err); } } @@ -1057,8 +1068,8 @@ function showInventoryWindow(name) { invContent.appendChild(count); }) .catch(err => { + handleError('Inventory', err, true); loading.textContent = `Failed to load inventory: ${err.message}`; - console.error('Inventory fetch failed:', err); }); debugLog('Inventory window created for:', name); @@ -1365,7 +1376,7 @@ async function pollLive() { renderTrails(trails); renderList(); } catch (e) { - console.error('Live or trails fetch failed:', e); + handleError('Player update', e); } } @@ -1375,7 +1386,7 @@ async function pollTotalRares() { const data = await response.json(); updateTotalRaresDisplay(data); } catch (e) { - console.error('Total rares fetch failed:', e); + handleError('Rare counter', e); } } @@ -1394,7 +1405,7 @@ async function pollTotalKills() { const data = await response.json(); updateTotalKillsDisplay(data); } catch (e) { - console.error('Total kills fetch failed:', e); + handleError('Kill counter', e); } } @@ -1411,7 +1422,7 @@ async function pollServerHealth() { const data = await response.json(); updateServerStatusDisplay(data); } catch (e) { - console.error('Server health fetch failed:', e); + handleError('Server health', e); updateServerStatusDisplay({ status: 'error' }); } } @@ -1835,7 +1846,7 @@ function initWebSocket() { } }); socket.addEventListener('close', () => setTimeout(initWebSocket, 2000)); - socket.addEventListener('error', e => console.error('WebSocket error:', e)); + socket.addEventListener('error', e => handleError('WebSocket', e)); } // Display or create a chat window for a character diff --git a/static/style.css b/static/style.css index bb903112..b17b8d70 100644 --- a/static/style.css +++ b/static/style.css @@ -1570,3 +1570,23 @@ body.noselect, body.noselect * { color: #88ccff; } +/* Error Toast */ +.error-toast { + position: fixed; + bottom: 20px; + right: 20px; + background: rgba(220, 38, 38, 0.9); + color: white; + padding: 12px 20px; + border-radius: 8px; + font-size: 13px; + z-index: 99999; + animation: toastFadeIn 0.3s ease; + max-width: 400px; +} + +@keyframes toastFadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} +