fix(02): CSS-safe occurrence ids + error boundary to surface render errors
Schedule-X rejects ids containing ':' '[' ']' (the old ${uid}::${iso} form) — mint ev-<uid>-<epochMs> instead. Add an ErrorBoundary so a render throw shows the error instead of a blank page.
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* ErrorBoundary — catches render/runtime errors in the calendar tree and shows a
|
||||
* readable fallback instead of a blank white page.
|
||||
*
|
||||
* A white-screen-on-error calendar is unacceptable UX (the non-technical Apple member
|
||||
* would just see nothing). This boundary degrades gracefully and surfaces the error
|
||||
* text + component stack so failures are diagnosable in the field.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
error: Error | null
|
||||
info: string | null
|
||||
}
|
||||
|
||||
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
state: ErrorBoundaryState = { error: null, info: null }
|
||||
|
||||
static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
|
||||
return { error }
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, info: React.ErrorInfo) {
|
||||
// Log for DevTools / future telemetry; also kept in state for on-screen display.
|
||||
console.error('[CalendarShell crash]', error, info.componentStack)
|
||||
this.setState({ info: info.componentStack ?? null })
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return (
|
||||
<div
|
||||
role="alert"
|
||||
style={{
|
||||
padding: '24px',
|
||||
fontFamily: 'system-ui, sans-serif',
|
||||
color: '#7f1d1d',
|
||||
background: '#fef2f2',
|
||||
height: '100dvh',
|
||||
overflow: 'auto',
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
>
|
||||
<h2 style={{ marginTop: 0 }}>The calendar hit an error</h2>
|
||||
<p style={{ fontWeight: 600 }}>{String(this.state.error?.message ?? this.state.error)}</p>
|
||||
<pre
|
||||
style={{
|
||||
whiteSpace: 'pre-wrap',
|
||||
fontSize: '12px',
|
||||
background: '#fff',
|
||||
border: '1px solid #fecaca',
|
||||
borderRadius: '6px',
|
||||
padding: '12px',
|
||||
}}
|
||||
>
|
||||
{this.state.error?.stack}
|
||||
{this.state.info ? '\n\n--- component stack ---' + this.state.info : ''}
|
||||
</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return this.props.children
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import App from './App.js'
|
||||
import { ErrorBoundary } from './components/ErrorBoundary.js'
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
@@ -25,7 +26,9 @@ const queryClient = new QueryClient({
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<App />
|
||||
<ErrorBoundary>
|
||||
<App />
|
||||
</ErrorBoundary>
|
||||
</QueryClientProvider>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user