Two problems from the first alpha4 run reported as "blank window + CSP
warning in devtools":
1. CSP was set via <meta> in index.html with a strict policy (script-src
'self'). Vite's dev server uses eval() for HMR, which the strict CSP
blocked at module-load time, so the renderer never ran. The meta CSP
also conflicted with Electron's own security heuristics (hence the
warning even though *we* had a policy — Electron was looking for it
on the HTTP response).
Moved the CSP to electron/main.ts via session.webRequest
.onHeadersReceived. Dev profile enables 'unsafe-eval' + ws:/wss: for
HMR; production profile stays strict (no eval, no remote scripts,
connect-src still wide because the user picks arbitrary node URLs).
2. When window.dchain isn't available (preload failed to load, dev
misconfig, etc.), loadKeyFile() throws inside a useEffect. React
swallows async-effect throws, so the app renders blank forever.
Added:
- requireDchain() guard in storage.ts with an explicit error.
- App.tsx catches boot-effect errors and renders them inline.
- ErrorBoundary.tsx for render-time throws.
- window.addEventListener('error') in main.tsx as a last-resort
paint for throws that escape React entirely.
Also: npm script electron:dev now rebuilds main.ts before spawning
Electron (was a silent concurrency bug — TypeScript errors in main.ts
would produce stale dist-electron/).
56 lines
1.7 KiB
TypeScript
56 lines
1.7 KiB
TypeScript
// Top-level error boundary. React eats thrown errors silently by default,
|
|
// which in an Electron app with no URL bar means "blank window, nothing
|
|
// to click" from the user's perspective. This component at least shows
|
|
// the error text + stack so we can copy-paste it into a bug report.
|
|
|
|
import React from 'react';
|
|
|
|
interface State {
|
|
error: Error | null;
|
|
}
|
|
|
|
export class ErrorBoundary extends React.Component<
|
|
{ children: React.ReactNode }, State
|
|
> {
|
|
state: State = { error: null };
|
|
|
|
static getDerivedStateFromError(error: Error): State {
|
|
return { error };
|
|
}
|
|
|
|
componentDidCatch(error: Error, info: React.ErrorInfo): void {
|
|
// Surface the exception in the devtools console too, for quick
|
|
// copy-paste when the boundary is blocking the UI.
|
|
console.error('[ErrorBoundary]', error, info);
|
|
}
|
|
|
|
render(): React.ReactNode {
|
|
if (!this.state.error) return this.props.children;
|
|
return (
|
|
<div style={{
|
|
padding: 24, height: '100%', overflow: 'auto',
|
|
background: '#000', color: '#fff', fontFamily: 'monospace',
|
|
}}>
|
|
<h2 style={{ color: '#ff6b6b', marginTop: 0 }}>Something broke.</h2>
|
|
<p style={{ color: '#fff' }}>{this.state.error.message}</p>
|
|
<pre style={{
|
|
color: '#8b8b8b', fontSize: 12, lineHeight: 1.4,
|
|
whiteSpace: 'pre-wrap', wordBreak: 'break-word',
|
|
}}>
|
|
{this.state.error.stack}
|
|
</pre>
|
|
<button
|
|
onClick={() => this.setState({ error: null })}
|
|
style={{
|
|
marginTop: 12, padding: '8px 14px', borderRadius: 999,
|
|
border: '1px solid #1f1f1f', background: '#111',
|
|
color: '#fff', cursor: 'pointer',
|
|
}}
|
|
>
|
|
Retry
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
}
|