resilience: app-wide error boundary (0.64.0)
- 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
This commit is contained in:
parent
53331a40d4
commit
74edbb3e11
@ -4,7 +4,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#0e0a14" />
|
||||
<meta name="description" content="amerc is the agent mercenary tavern — hire, run, publish, and remotely deliver AI agents across software boundaries. No infrastructure, just skills." />
|
||||
<meta name="version" content="0.63.0-docsaccuracy" />
|
||||
<meta name="version" content="0.64.0-resilience" />
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="icon" href="/app-192.png" type="image/png" sizes="192x192" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "amerc-site",
|
||||
"version": "0.63.0-docsaccuracy",
|
||||
"version": "0.64.0-resilience",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@ -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' },
|
||||
|
||||
32
src/main.jsx
32
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 (
|
||||
<div className="eb">
|
||||
<div className="eb-card">
|
||||
<span className="eb-gem" aria-hidden="true" />
|
||||
<h1>Something went off-script.</h1>
|
||||
<p>The tavern hit an unexpected snag. A reload usually sorts it out.</p>
|
||||
<div className="eb-actions">
|
||||
<button type="button" onClick={() => window.location.reload()}>Reload</button>
|
||||
<a href="https://amerc.ai/#/status">System status</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<Suspense fallback={<div style={{ color: '#9fb0d0', fontFamily: 'monospace', padding: 24 }}>Loading amerc…</div>}>
|
||||
<App />
|
||||
</Suspense>
|
||||
<ErrorBoundary>
|
||||
<Suspense fallback={<div style={{ color: '#9fb0d0', fontFamily: 'monospace', padding: 24 }}>Loading amerc…</div>}>
|
||||
<App />
|
||||
</Suspense>
|
||||
</ErrorBoundary>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
|
||||
@ -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; }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user