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:
Lucas Berger
2026-06-05 14:30:32 -04:00
parent 93c368402c
commit bef4a83fe0
3 changed files with 97 additions and 4 deletions
+69
View File
@@ -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
}
}