From 74edbb3e1174423a32be9693fce333bf92a4136c Mon Sep 17 00:00:00 2001 From: artheru Date: Wed, 10 Jun 2026 08:55:46 +0800 Subject: [PATCH] resilience: app-wide error boundary (0.64.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - previously, any render error would unmount the whole SPA to a blank white screen (no boundary) - add a React error boundary around the routed app (all sub-apps: tavern, docs, pm) with an on-brand fallback — gem, 'Something went off-script', Reload + System status links — instead of a void - verified the catch path locally (a temporary ?boom trigger rendered the fallback), confirmed the boundary is transparent in normal use, then removed the trigger; verified live renders normally --- index.html | 2 +- package.json | 2 +- src/Scene2D.jsx | 2 +- src/main.jsx | 32 +++++++++++++++++++++++++++++--- src/styles.css | 11 +++++++++++ 5 files changed, 43 insertions(+), 6 deletions(-) diff --git a/index.html b/index.html index 5880668..8edfef4 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - + diff --git a/package.json b/package.json index 2926609..36c7d05 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "amerc-site", - "version": "0.63.0-docsaccuracy", + "version": "0.64.0-resilience", "private": true, "type": "module", "scripts": { diff --git a/src/Scene2D.jsx b/src/Scene2D.jsx index f6e4513..5c085bd 100644 --- a/src/Scene2D.jsx +++ b/src/Scene2D.jsx @@ -7,7 +7,7 @@ import Account from './Account.jsx'; import TavernGame from './TavernGame.jsx'; import { api } from './api.js'; -const RELEASE = '0.63.0-docsaccuracy'; +const RELEASE = '0.64.0-resilience'; const NAV = [ { id: 'home', label: 'Tavern' }, diff --git a/src/main.jsx b/src/main.jsx index 2b1bd53..43c96ca 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -12,11 +12,37 @@ let App = Scene2DApp; if (host.startsWith('docs.')) App = DocsApp; else if (host.startsWith('pm.')) App = PmApp; +// Catch render errors so a single bad component can't blank the whole app. +class ErrorBoundary extends React.Component { + constructor(props) { super(props); this.state = { error: null }; } + static getDerivedStateFromError(error) { return { error }; } + render() { + if (this.state.error) { + return ( +
+
+
+
+ ); + } + return this.props.children; + } +} + createRoot(document.getElementById('root')).render( - Loading amerc…}> - - + + Loading amerc…}> + + + , ); diff --git a/src/styles.css b/src/styles.css index 5619c50..67d2228 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1435,3 +1435,14 @@ button { /* themed task-list checkboxes in docs (0.63) */ .docs-render input[type="checkbox"] { accent-color: var(--accent); width: 14px; height: 14px; vertical-align: -2px; margin-right: 6px; } .docs-render li:has(> input[type="checkbox"]) { list-style: none; margin-left: -20px; } + +/* error boundary fallback (0.64) */ +.eb { position: fixed; inset: 0; display: grid; place-items: center; background: radial-gradient(circle at 50% 28%, #160d22, #07060c 70%); padding: 24px; } +.eb-card { max-width: 420px; text-align: center; background: linear-gradient(180deg, #160f24, #110b1d); border: 1px solid #2a1f3e; border-radius: 16px; padding: 32px 28px; box-shadow: 0 20px 60px rgba(0,0,0,0.5); font-family: Georgia, "Times New Roman", serif; } +.eb-gem { width: 26px; height: 26px; display: inline-block; background: linear-gradient(135deg, #5fe6ff, #1f86c4); transform: rotate(45deg); box-shadow: 0 0 0 3px #07273a, 0 0 24px rgba(39,216,255,0.5); margin-bottom: 20px; } +.eb-card h1 { color: #f3d27a; font-size: 22px; margin: 0 0 10px; } +.eb-card p { color: #b6a7d4; font-size: 14px; line-height: 1.6; margin: 0 0 22px; } +.eb-actions { display: flex; gap: 12px; justify-content: center; align-items: center; } +.eb-actions button { font-family: Georgia, serif; font-size: 14px; padding: 9px 22px; border-radius: 10px; border: none; cursor: pointer; background: linear-gradient(180deg, #f3d27a, #d9a948); color: #2a1303; font-weight: 600; } +.eb-actions a { font-family: Georgia, serif; font-size: 14px; color: #cfe6ff; text-decoration: none; } +.eb-actions a:hover { color: #7fe9ff; }