Keep a page error or a busy engine from blanking the app
A user on Upload got a white page while the engine's proxy answered many requests with nginx's rate-limit 503. - Pages render inside an error boundary, so a crash shows a message with Try again and Reload instead of unmounting the whole app. - A 503 or 429 without the engine's JSON reads "The engine is busy", and queries wait 5s, then 10s, before asking again instead of 1s. - Upload asks for knowledge details two at a time and reuses them until the document's knowledge status changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { LANGS, locale, setLang, t, useLang, type Key } from '../i18n'
|
||||
import { workspaceCredits } from '../api/engine'
|
||||
import { Brand, Icon, type IconName } from './Icon'
|
||||
import { Toaster } from './Toast'
|
||||
import { ErrorBoundary } from './ErrorBoundary'
|
||||
|
||||
// Learning Studio (learning-lab/frontend); both apps share one Clerk instance, so the user stays signed in.
|
||||
const STUDIO_URL: string = import.meta.env.VITE_STUDIO_URL
|
||||
@@ -208,7 +209,7 @@ export function AppLayout() {
|
||||
</div>
|
||||
</header>
|
||||
{/* Keyed by route so each page plays the fade-in from theme.css. */}
|
||||
<main tabIndex={-1} key={pathname}><Outlet /></main>
|
||||
<main tabIndex={-1} key={pathname}><ErrorBoundary><Outlet /></ErrorBoundary></main>
|
||||
<footer className="app-footer">Learning Lab Express <span>by BAWANA</span><span>{t('layout.footer')}</span></footer>
|
||||
</div>
|
||||
{help && <HelpDialog onClose={() => setHelp(false)} />}
|
||||
|
||||
35
src/components/ErrorBoundary.tsx
Normal file
35
src/components/ErrorBoundary.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Component, type ReactNode } from 'react'
|
||||
import { t } from '../i18n'
|
||||
import { Empty } from './Page'
|
||||
|
||||
// Without this, one component throwing while rendering unmounts the whole app and leaves a white
|
||||
// page. The layout keys it by route, so moving to another page clears the error.
|
||||
export class ErrorBoundary extends Component<{ children: ReactNode }, { error: Error | null }> {
|
||||
state = { error: null as Error | null }
|
||||
|
||||
static getDerivedStateFromError(error: Error) {
|
||||
return { error }
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error) {
|
||||
console.error('Page crashed', error)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { error } = this.state
|
||||
if (!error) return this.props.children
|
||||
return (
|
||||
<div className="card">
|
||||
<Empty icon="info" title={t('err.crashTitle')}>
|
||||
{t('err.crashCopy')}
|
||||
<br />
|
||||
<small>{error.message}</small>
|
||||
</Empty>
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: 8, paddingBottom: 24 }}>
|
||||
<button className="btn small" onClick={() => this.setState({ error: null })}>{t('common.retry')}</button>
|
||||
<button className="btn small primary" onClick={() => window.location.reload()}>{t('err.reload')}</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user