diff --git a/diagnostic/build-2b707695-part001.logd b/diagnostic/build-2b707695-part001.logd
new file mode 100644
index 00000000..cdf18694
Binary files /dev/null and b/diagnostic/build-2b707695-part001.logd differ
diff --git a/diagnostic/build-2b707695-part002.logd b/diagnostic/build-2b707695-part002.logd
new file mode 100644
index 00000000..a403aedc
Binary files /dev/null and b/diagnostic/build-2b707695-part002.logd differ
diff --git a/diagnostic/build-2b707695-part003.logd b/diagnostic/build-2b707695-part003.logd
new file mode 100644
index 00000000..72d590a0
Binary files /dev/null and b/diagnostic/build-2b707695-part003.logd differ
diff --git a/diagnostic/build-2b707695-part004.logd b/diagnostic/build-2b707695-part004.logd
new file mode 100644
index 00000000..3f3c790f
Binary files /dev/null and b/diagnostic/build-2b707695-part004.logd differ
diff --git a/diagnostic/build-2b707695.json b/diagnostic/build-2b707695.json
new file mode 100644
index 00000000..4fcbbd68
--- /dev/null
+++ b/diagnostic/build-2b707695.json
@@ -0,0 +1,29 @@
+{
+ "generated_at": "2026-06-23T20:40:04.002065+00:00",
+ "commit": "2b707695",
+ "diagnostic_logd": [
+ "diagnostic\\build-2b707695-part001.logd",
+ "diagnostic\\build-2b707695-part002.logd",
+ "diagnostic\\build-2b707695-part003.logd",
+ "diagnostic\\build-2b707695-part004.logd"
+ ],
+ "diagnostic_logd_error": null,
+ "message_blocker": null,
+ "chunked": true,
+ "chunk_size_bytes": 41943040,
+ "password": "abd282865073fcbdd0f7",
+ "decrypt_command": "encryptly unpack diagnostic\\build-2b707695.logd --password abd282865073fcbdd0f7",
+ "total_modules": 1,
+ "passed": 1,
+ "failed": 0,
+ "modules": [
+ {
+ "name": "frontend",
+ "status": "PASS",
+ "elapsed_seconds": 4.772,
+ "artifact": "D:\\\u6211\u7684\u575a\u679c\u4e91\\\u4e2a\u4eba\\SODA\u8ba1\u5212\\github-bounty-work\\tentoftrials-error-boundaries\\frontend\\dist",
+ "output": ""
+ }
+ ],
+ "pr_note": "Include the encrypted diagnostic logd artifact(s): diagnostic\\build-2b707695-part001.logd, diagnostic\\build-2b707695-part002.logd, diagnostic\\build-2b707695-part003.logd, diagnostic\\build-2b707695-part004.logd. The encrypted .logd is the required diagnostic content for PR review; this JSON file is metadata. Maintainers may ask you to remove these diagnostic artifacts before merging."
+}
diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx
new file mode 100644
index 00000000..c71804f2
--- /dev/null
+++ b/frontend/src/components/ErrorBoundary.tsx
@@ -0,0 +1,64 @@
+import React from 'react';
+
+interface ErrorBoundaryProps {
+ children: React.ReactNode;
+ fallbackTitle?: string;
+ fallbackMessage?: string;
+}
+
+interface ErrorBoundaryState {
+ hasError: boolean;
+}
+
+const fallbackStyle: React.CSSProperties = {
+ border: '1px solid rgba(220, 38, 38, 0.28)',
+ borderRadius: 12,
+ background: 'rgba(254, 242, 242, 0.96)',
+ color: '#7f1d1d',
+ padding: '1rem',
+};
+
+const fallbackTitleStyle: React.CSSProperties = {
+ margin: '0 0 0.35rem',
+ fontSize: '1rem',
+ fontWeight: 700,
+};
+
+const fallbackMessageStyle: React.CSSProperties = {
+ margin: 0,
+ color: '#991b1b',
+};
+
+class ErrorBoundary extends React.Component {
+ state: ErrorBoundaryState = {
+ hasError: false,
+ };
+
+ static getDerivedStateFromError(): ErrorBoundaryState {
+ return { hasError: true };
+ }
+
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
+ console.error('Dashboard render error boundary caught an error', error, errorInfo);
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return (
+
+
+ {this.props.fallbackTitle ?? 'This section could not be displayed'}
+
+
+ {this.props.fallbackMessage ??
+ 'The rest of the dashboard is still available. Refresh the page or try again shortly.'}
+
+
+ );
+ }
+
+ return this.props.children;
+ }
+}
+
+export default ErrorBoundary;
diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx
index 447dcfa3..7c777874 100644
--- a/frontend/src/pages/Dashboard.tsx
+++ b/frontend/src/pages/Dashboard.tsx
@@ -1,4 +1,5 @@
import React from 'react';
+import ErrorBoundary from '../components/ErrorBoundary';
import { useDashboardStats } from '../hooks';
const statCards = [
@@ -10,7 +11,34 @@ const statCards = [
{ key: 'uptime', label: 'Uptime', color: '#0891b2', suffix: '%' },
];
-const Dashboard: React.FC = () => {
+interface StatsGridProps {
+ stats: Record | undefined;
+}
+
+const StatsGrid: React.FC = ({ stats }) => (
+
+ {statCards.map((card) => (
+
+
+
+ {card.label}
+
+ {String(stats?.[card.key] ?? ' - ')}
+ {card.suffix || ''}
+
+
+
+ ))}
+
+);
+
+const DashboardContent: React.FC = () => {
const { data: stats, isLoading, error } = useDashboardStats();
if (isLoading) {
@@ -39,26 +67,12 @@ const Dashboard: React.FC = () => {
-
- {statCards.map((card) => (
-
-
-
- {card.label}
-
- {String((stats as any)?.[card.key] ?? ' - ')}
- {card.suffix || ''}
-
-
-
- ))}
-
+
+ | undefined} />
+
@@ -78,4 +92,13 @@ const Dashboard: React.FC = () => {
);
};
+const Dashboard: React.FC = () => (
+
+
+
+);
+
export default Dashboard;