diff --git a/diagnostic/build-0dde0a52-part001.logd b/diagnostic/build-0dde0a52-part001.logd
new file mode 100644
index 00000000..374181be
Binary files /dev/null and b/diagnostic/build-0dde0a52-part001.logd differ
diff --git a/diagnostic/build-0dde0a52-part002.logd b/diagnostic/build-0dde0a52-part002.logd
new file mode 100644
index 00000000..c46818e4
Binary files /dev/null and b/diagnostic/build-0dde0a52-part002.logd differ
diff --git a/diagnostic/build-0dde0a52-part003.logd b/diagnostic/build-0dde0a52-part003.logd
new file mode 100644
index 00000000..b2e38366
Binary files /dev/null and b/diagnostic/build-0dde0a52-part003.logd differ
diff --git a/diagnostic/build-0dde0a52-part004.logd b/diagnostic/build-0dde0a52-part004.logd
new file mode 100644
index 00000000..c014df45
Binary files /dev/null and b/diagnostic/build-0dde0a52-part004.logd differ
diff --git a/diagnostic/build-0dde0a52-part005.logd b/diagnostic/build-0dde0a52-part005.logd
new file mode 100644
index 00000000..d24ff205
Binary files /dev/null and b/diagnostic/build-0dde0a52-part005.logd differ
diff --git a/diagnostic/build-0dde0a52-part006.logd b/diagnostic/build-0dde0a52-part006.logd
new file mode 100644
index 00000000..0cf86932
Binary files /dev/null and b/diagnostic/build-0dde0a52-part006.logd differ
diff --git a/diagnostic/build-0dde0a52.json b/diagnostic/build-0dde0a52.json
new file mode 100644
index 00000000..1352ca76
--- /dev/null
+++ b/diagnostic/build-0dde0a52.json
@@ -0,0 +1,31 @@
+{
+ "generated_at": "2026-06-27T01:04:00.344287+00:00",
+ "commit": "0dde0a52",
+ "diagnostic_logd": [
+ "diagnostic\\build-0dde0a52-part001.logd",
+ "diagnostic\\build-0dde0a52-part002.logd",
+ "diagnostic\\build-0dde0a52-part003.logd",
+ "diagnostic\\build-0dde0a52-part004.logd",
+ "diagnostic\\build-0dde0a52-part005.logd",
+ "diagnostic\\build-0dde0a52-part006.logd"
+ ],
+ "diagnostic_logd_error": null,
+ "message_blocker": null,
+ "chunked": true,
+ "chunk_size_bytes": 41943040,
+ "password": "994378c16a1a357d6bdf",
+ "decrypt_command": "encryptly unpack diagnostic\\build-0dde0a52.logd --password 994378c16a1a357d6bdf",
+ "total_modules": 1,
+ "passed": 1,
+ "failed": 0,
+ "modules": [
+ {
+ "name": "frontend",
+ "status": "PASS",
+ "elapsed_seconds": 8.478,
+ "artifact": "C:\\Users\\FLOR\\Documents\\Codex\\2026-06-26\\g\\work\\therapy-tentoftrials-submit\\frontend\\dist",
+ "output": "> tent-frontend@0.0.0 build\n> tsc -b && vite build\n\n\u001b[36mvite v6.4.3 \u001b[32mbuilding for production...\u001b[36m\u001b[39m\ntransforming...\n\u001b[32m\u2713\u001b[39m 102 modules transformed.\nrendering chunks...\ncomputing gzip size...\n\u001b[2mdist/\u001b[22m\u001b[32mindex.html \u001b[39m\u001b[1m\u001b[2m 0.71 kB\u001b[22m\u001b[1m\u001b[22m\u001b[2m \u2502 gzip: 0.38 kB\u001b[22m\n\u001b[2mdist/\u001b[22m\u001b[35massets/index-C4shL4Bc.css \u001b[39m\u001b[1m\u001b[2m 0.50 kB\u001b[22m\u001b[1m\u001b[22m\u001b[2m \u2502 gzip: 0.23 kB\u001b[22m\n\u001b[2mdist/\u001b[22m\u001b[36massets/state-BkjSKDbY.js \u001b[39m\u001b[1m\u001b[2m 8.91 kB\u001b[22m\u001b[1m\u001b[22m\u001b[2m \u2502 gzip: 3.54 kB\u001b[22m\u001b[2m \u2502 map: 57.15 kB\u001b[22m\n\u001b[2mdist/\u001b[22m\u001b[36massets/vendor-CREcWLHI.js \u001b[39m\u001b[1m\u001b[2m 48.93 kB\u001b[22m\u001b[1m\u001b[22m\u001b[2m \u2502 gzip: 17.25 kB\u001b[22m\u001b[2m \u2502 map: 481.27 kB\u001b[22m\n\u001b[2mdist/\u001b[22m\u001b[36massets/index-BHsknpR9.js \u001b[39m\u001b[1m\u001b[2m232.92 kB\u001b[22m\u001b[1m\u001b[22m\u001b[2m \u2502 gzip: 72.65 kB\u001b[22m\u001b[2m \u2502 map: 1,049.91 kB\u001b[22m\n\u001b[32m\u2713 built in 2.02s\u001b[39m"
+ }
+ ],
+ "pr_note": "Include the encrypted diagnostic logd artifact(s): diagnostic\\build-0dde0a52-part001.logd, diagnostic\\build-0dde0a52-part002.logd, diagnostic\\build-0dde0a52-part003.logd, diagnostic\\build-0dde0a52-part004.logd, diagnostic\\build-0dde0a52-part005.logd, diagnostic\\build-0dde0a52-part006.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/App.tsx b/frontend/src/App.tsx
index 5e3ad158..cbe4ef3d 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import Layout from './components/Layout';
+import ErrorBoundary from './components/ErrorBoundary';
import Dashboard from './pages/Dashboard';
import Analytics from './pages/Analytics';
import Settings from './pages/Settings';
@@ -9,7 +10,17 @@ const App: React.FC = () => {
return (
- } />
+
+
+
+ }
+ />
} />
} />
} />
diff --git a/frontend/src/components/ErrorBoundary.css b/frontend/src/components/ErrorBoundary.css
new file mode 100644
index 00000000..656eaa22
--- /dev/null
+++ b/frontend/src/components/ErrorBoundary.css
@@ -0,0 +1,37 @@
+.error-boundary-fallback {
+ background: #1e293b;
+ border: 1px solid #334155;
+ border-radius: 8px;
+ color: #f8fafc;
+ padding: 1rem;
+}
+
+.error-boundary-fallback.compact {
+ padding: 0.75rem;
+}
+
+.error-boundary-fallback h2,
+.error-boundary-fallback h3 {
+ color: #f8fafc;
+ margin: 0 0 0.5rem;
+}
+
+.error-boundary-fallback h2 {
+ font-size: 1.25rem;
+}
+
+.error-boundary-fallback h3 {
+ font-size: 1rem;
+}
+
+.error-boundary-fallback p {
+ color: #94a3b8;
+ margin: 0;
+}
+
+.error-boundary-fallback .error-boundary-detail {
+ color: #eab308;
+ display: block;
+ font-size: 0.875rem;
+ margin-top: 0.75rem;
+}
diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx
new file mode 100644
index 00000000..7754ec77
--- /dev/null
+++ b/frontend/src/components/ErrorBoundary.tsx
@@ -0,0 +1,55 @@
+import React from 'react';
+import './ErrorBoundary.css';
+
+interface ErrorBoundaryProps {
+ children: React.ReactNode;
+ fallbackTitle?: string;
+ fallbackMessage?: string;
+ compact?: boolean;
+}
+
+interface ErrorBoundaryState {
+ error: Error | null;
+}
+
+class ErrorBoundary extends React.Component {
+ state: ErrorBoundaryState = {
+ error: null,
+ };
+
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
+ return { error };
+ }
+
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
+ console.error('[ErrorBoundary] Render failure isolated:', error, errorInfo);
+ }
+
+ render() {
+ if (this.state.error) {
+ const {
+ fallbackTitle = 'Section unavailable',
+ fallbackMessage = 'This part of the interface could not be rendered. The rest of the application is still available.',
+ compact = false,
+ } = this.props;
+
+ return (
+
+ {compact ? {fallbackTitle}
: {fallbackTitle}
}
+ {fallbackMessage}
+ {import.meta.env.DEV ? (
+ {this.state.error.message}
+ ) : null}
+
+ );
+ }
+
+ return this.props.children;
+ }
+}
+
+export default ErrorBoundary;
diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx
index 7f23c536..8d704d73 100644
--- a/frontend/src/components/Layout.tsx
+++ b/frontend/src/components/Layout.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import Header from './Header';
import Sidebar from './Sidebar';
+import ErrorBoundary from './ErrorBoundary';
interface LayoutProps {
children: React.ReactNode;
@@ -11,13 +12,30 @@ const Layout: React.FC = ({ children }) => {
return (
diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx
index 447dcfa3..0fdd750e 100644
--- a/frontend/src/pages/Dashboard.tsx
+++ b/frontend/src/pages/Dashboard.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import { useDashboardStats } from '../hooks';
+import ErrorBoundary from '../components/ErrorBoundary';
const statCards = [
{ key: 'totalUsers', label: 'Total Users', color: '#4f46e5' },
@@ -10,6 +11,30 @@ const statCards = [
{ key: 'uptime', label: 'Uptime', color: '#0891b2', suffix: '%' },
];
+interface StatCardProps {
+ card: typeof statCards[number];
+ value: unknown;
+}
+
+const StatCard: React.FC = ({ card, value }) => (
+
+
+
+ {card.label}
+
+ {String(value ?? ' - ')}
+ {card.suffix || ''}
+
+
+
+);
+
const Dashboard: React.FC = () => {
const { data: stats, isLoading, error } = useDashboardStats();
@@ -39,26 +64,21 @@ const Dashboard: React.FC = () => {
-
- {statCards.map((card) => (
-
-
+
+ {statCards.map((card) => (
+
-
- {card.label}
-
- {String((stats as any)?.[card.key] ?? ' - ')}
- {card.suffix || ''}
-
-
-
- ))}
-
+ ))}
+
+